MySQL Master-Master проблема репликации и автоинкрементного столбца - PullRequest
2 голосов
/ 19 ноября 2011

Я провожу некоторое тестирование с репликацией мастер-мастер, и я столкнулся с какой-то странной проблемой, я постараюсь описать процедуру, которой я следовал, чтобы кто-то мог воспроизвести проблему.

Я настроил репликацию на 2 ВМ и в файле конфигурации для каждой:

-- Master1 -- 
auto_increment_increment = 2
auto_increment_offset = 1

-- Master2 -- 
auto_increment_increment = 2
auto_increment_offset = 2

Эти настройки должны приводить к арифметической прогрессии для столбцов с автоинкрементом:

- Master1: 1,3,5,7,9,11,13  ...
- Master2: 2,4,6,8,10,12,14 ...

Master1 получает нечетные числа, а Master2 получает четные. Затем я создаю тестовую базу данных и добавляю таблицу со следующим определением:

CREATE TABLE `t1` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `c1` varchar(50) DEFAULT NULL,
 `d1` date DEFAULT '1970-01-01',
  PRIMARY KEY (`id`)
) ENGINE=MyISAM;

Конечно, база данных создается на обоих серверах. После этого я выполняю

START SLAVE;

на обоих серверах, чтобы началась репликация. Для генерации данных я использую следующую процедуру:

  • Для запуска процесса должна быть вставлена ​​одна запись

    INSERT INTO t1 (c1, d1) ВЫБРАТЬ LPAD ('', 50, MD5 (RAND ())), DATE_ADD (CURDATE (), ИНТЕРВАЛЬНЫЙ ЭТАЖ (RAND () * 365) ДЕНЬ);

  • Затем вы используете INSERT - SELECT из той же таблицы, которая начнет вставлять со скоростью 2 n , n - время выполнения запроса:

    INSERT INTO t1 (c1, d1) ВЫБРАТЬ LPAD ('', 50, MD5 (RAND ())), DATE_ADD (CURDATE (), ИНТЕРВАЛЬНЫЙ ЭТАЖ (RAND () * 365) ДЕНЬ) ОТ t1;

Совет: Я считаю, что описанный здесь метод также очень удобен для генерации случайных данных для ваших таблиц.

Итак, когда я начинаю выполнять эти запросы одновременно на обоих серверах, это всегда приводит к ошибке дублирования ключа репликации для столбца автоинкремента. Если у кого-то есть идеи, буду благодарен!

PS: Конечно, такого рода запросы редко бывают в производственных приложениях, но я считаю, что это все еще доказывает.

1 Ответ

4 голосов
/ 20 ноября 2011

ПРИМЕЧАНИЕ: Я нашел ответ и поставил его сверху.Ниже представлен ответ на некоторые другие разглагольствования (мой первоначальный ответ), которые все еще могут содержать некоторое значение, чтобы объяснить это.

Поскольку ваш запрос удваивает количество строк, ваш оператор INSERT INTO t1(c1,d1) SELECT LPAD('', 50, MD5( RAND() ) ), DATE_ADD( CURDATE(), INTERVAL FLOOR( RAND() * 365 ) DAY ) FROM t1; может вставить на сервер различное количество строк1 и сервер 2. Все операторы, которые используют столбец с автоинкрементом, отправляют свой INSERT_ID вместе с репликацией, и это значение не будет иметь значение true на сервере 2, если оператор также выполнялся там.

Давайте посмотримна примере.Я сделаю stop slave для имитации длительного выполнения запроса или плохой сети.

  1. Создайте две базы данных и настройте репликацию мастер-мастер
  2. Создайте таблицу и вставьте начальную строку
  3. Остановить репликацию на сервере 2
  4. Запустить оператор, который удваивает количество строк пару раз на сервере 1. 2 достаточно, но я сделал 3.
  5. Проверьте show binlog events (предупреждение, не делайте этого на старой базе данных, это займет вечность).Это то, что я вижу.

    Запрос |НАЧАЛО
    Интвар |INSERT_ID = 3
    Запрос |используйте test;INSERT INTO t1 (c1, d1) SELECT ...
    Запрос |COMMIT
    Запрос |НАЧАЛО
    Интвар |INSERT_ID = 5
    Запрос |используйте test;INSERT INTO t1 (c1, d1) SELECT ...
    Запрос |COMMIT
    Запрос |НАЧАЛО
    Интвар |INSERT_ID = 9
    Запрос |используйте test;INSERT INTO t1 (c1, d1) SELECT ... Запрос |COMMIT

  6. Обратите внимание, что при каждом запуске дублирование INSERT_ID изменяется соответственно.На второй вставке это 5 означает, что на первой вставке вставлена ​​1 строка (помните, приращение равно 2).На третьей вставке INSERT_ID равен 9, что означает, что на второй вставке вставлено 2 строки.Это все имеет смысл.Давайте продолжим

  7. На сервере 2 сделайте дублирование один раз, пока не запускайте репликацию.Выполнение select * from t1 теперь правильно отображает две строки с идентификаторами 1 и 2.

  8. Теперь снова запустите ведомое устройство и запустите SHOW SLAVE STATUS \G.Он остановился с дублирующим идентификатором 5. Выбор всех значений из t1 снова показывает четыре строки.Первый был начальным.Второе - это то, что мы сделали на сервере 2, и два последних раза с идентификаторами 3 и 5 были из того первого оператора на сервере 1, который добавил только 1 строку.

  9. Следующая часть репликации это

    Запрос |НАЧАЛО
    Интвар |INSERT_ID = 5
    Запрос |используйте test;INSERT INTO t1 (c1, d1) SELECT ...
    Запрос |COMMIT

  10. На сервере 1 INSERT_ID равнялся 5, когда это произошло, и именно это будет использовать репликация, однако на сервере 2 у нас уже есть идентификатор 5, потому что мы дублировали строки еще на однувремя, прежде чем получить это.Так что репликация обрывается.

Итог таков.При выполнении репликации мастер-мастер каждый оператор должен воздействовать на базу данных одинаково.Добавление или удаление того же количества строк среди других вещей.

Тем не менее, есть простой способ исправить этот конкретный случай, если вам нужно сделать что-то подобное.

  1. Добавьте к данным server_id и создайте таблицу, подобную этой

    CREATE TABLE t1 (id int (11) NOT NULL AUTO_INCREMENT, server_id int (1) DEFAULT NULL, c1 varchar(50) DEFAULT NULL, d1 date DEFAULT '1970-01-01', ПЕРВИЧНЫЙ КЛЮЧ (id)) ДВИГАТЕЛЬ = MyISAM AUTO_INCREMENT = 4 CHARSET DEFAULT = латиница 1;

  2. Подготовьте две строки, по одной для каждого идентификатора сервера

    INSERT INTO t1 (server_id, c1, d1) SELECT 1, LPAD ('', 50, MD5 (RAND ())), DATE_ADD (CURDATE (), INTERVALЭТАЖ (RAND () * 365) ДЕНЬ);INSERT INTO t1 (server_id, c1, d1) SELECT 2, LPAD ('', 50, MD5 (RAND ())), DATE_ADD (CURDATE (), промежуточный этаж (RAND () * 365) ДЕНЬ);

  3. Для каждого дублирования просто учитывайте строки, созданные на вашем сервере.

    INSERT INTO t1 (server_id, c1, d1) SELECT server_id, LPAD ('', 50, MD5 (RAND ())), DATE_ADD (CURDATE (), ИНТЕРВАЛЬНЫЙ ЭТАЖ (RAND () * 365) ДЕНЬ) ОТ t1где server_id = 1;

НИЖЕ ОРИГИНАЛЬНЫЙ ОТВЕТ

Прежде всего, вы ошибаетесь, если предположите, что у вас будет два набора идентификаторовранжированные 1, 3, 5, .. и 2, 4, 6 ... Независимо от того, на каком сервере выполняется оператор для значения, если значение Auto_increment всегда равно max (id) +1.Поэтому, если вы сделаете две вставки на сервере 1, он получит нечетные значения 1 и 3. Если вы сделаете одну вставку на сервере 2, он получит четное значение 4 (4 - это следующее число, большее 3, что удовлетворяет auto_increment_offset + N × auto_increment_increment ).

Вы можете увидеть значение Auto_increment, выполнив show table status;

Во-вторых, каждая из ваших вставок после первой удваивает количество строк в таблице, быстро делая ее очень медленной операцией, и яне удивлюсь, если это как-то связано с тем, что каждый запрос такой медленный.

Тем не менее, вот как я это проверил (и получил такие же удивительные результаты).

  1. Я создал новую пустую установку с двумя серверами и главным мастером make_replication_sandbox --master_master mysql-5.5.17-osx10.6-x86_64.tar.gz.Они оба запущены, и поэтому есть рабы.Они настраиваются автоматически в соответствии с вашими настройками.
  2. Затем я создал таблицу и вставил первую строку в соответствии с вашим вопросом.Auto_increment теперь равно 2 на обоих серверах, и в таблице есть одна строка
  3. Затем я запускаю while (true) do ./n1 test -e "INSERT INTO t1(c1,d1) SELECT LPAD('', 50, MD5( RAND() ) ), DATE_ADD( CURDATE(), INTERVAL FLOOR( RAND() * 365 ) DAY ) FROM t1;"; done; для обоих серверов одновременно (./n2 на другом).

И у меня есть теория.

Скажем, у вас есть 1000 строк в таблице, и вы инициируете одинаковое дублирование на обоих серверах одновременно.Одним словом, вы получите 4000 строк впоследствии на обоих серверах, и все они будут одинаковыми.

Но получается, что вы дублируете строки в каждой базе данных, так что сервер 1 видит 2000 строк и сервер 2000 строк, но только первая 1000 одинакова, другая 1000 была сгенерирована по-разному на двух серверах.

Затем начинается репликация. Это репликация на основе оператора, поэтому выполняется один и тот же оператор, означающий, что на обоих серверах строки снова дублируются до 4000, и это правильный счет, но все же только 1000 из них одинаковы,другие 3000 будут отличаться.

Пока каждый сервер выполняет одинаковое количество запросов, это может работать (без дубликатов, но данные различаются), но если одному серверу удается выполнить два запроса до того, как репликация кешируется, вы получаетеРепликация в репликации, что на сервере 2 добавлено 1000 строк (если раньше было 1000 строк), а на сервере 1 добавлено 4000 строк (потому что сервер 1 уже удвоил 1000 дважды).Если следующий оператор добавил еще 2000 строк на сервере 2, а двоичный журнал содержит что-то вроде «первое автоматическое увеличение, используемое на сервере», то вы получите коллизию.

Я знаю, что это абстрактно, странно и даже сложнееписать, чем думать об этом:)

Я надеюсь, что это помогает, и я надеюсь, что это проблема ... Мастер-мастер труден, и это определенно одна из вещей, которые я бы не хотелне делай в мастер-мастер.

...