Spec-Zone.ru › MySQL 9.2

25.7.12 Разрешение конфликтов при репликации в NDB Cluster

  • Требования

  • Управление столбцом источника

  • Настройка разрешения конфликтов

  • Функции разрешения конфликтов

  • Таблица исключений разрешения конфликтов

  • Переменные состояния обнаружения конфликтов

  • Примеры

При использовании конфигурации репликации с несколькими источниками (включая циклическую репликацию), возможно, что разные источники могут пытаться обновить одну и ту же строку на реплике с различными данными. Разрешение конфликтов в NDB Cluster Replication предоставляет способ разрешения таких конфликтов, позволяя использовать определяемый пользователем столбец разрешения для определения того, следует ли применять обновление из данного источника на реплике.

Некоторые типы разрешения конфликтов, поддерживаемые NDB Cluster (NDB$OLD(), NDB$MAX() и NDB$MAX_DELETE_WIN(); NDB$MAX_INS() и NDB$MAX_DEL_WIN_INS()) реализуют этот определяемый пользователем столбец как столбец “марки времени” (хотя его тип не может быть TIMESTAMP, как поясняется далее в этом разделе). Эти типы разрешения конфликтов всегда применяются на строке по строке, а не на уровне транзакции. Функции разрешения конфликтов, основанные на эпохе, NDB$EPOCH() и NDB$EPOCH_TRANS(), сравнивают порядок репликации эпох (и, следовательно, эти функции являются транзакционными). Различные методы могут использоваться для сравнения значений столбца разрешения на реплике при возникновении конфликтов, как поясняется далее в этом разделе; используемый метод может быть задан для действия на одной таблице, базе данных или сервере, или на наборе одной или нескольких таблиц с использованием шаблонов сопоставления. См. Сопоставление с подстановками для получения информации об использовании шаблонов подстановки в столбцах db, table_name и server_id таблицы mysql.ndb_replication.

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

Требования

Подготовительные действия для разрешения конфликтов должны быть выполнены как на источнике, так и на реплике. Эти задачи описаны в следующем списке:

  • На источнике, записывающем двоичные журналы, вы должны определить, какие столбцы отправлять (все столбцы или только те, которые были обновлены). Это выполняется для всего сервера MySQL путём применения параметра запуска mysqld --ndb-log-updated-only (описан далее в этом разделе), или для одной или нескольких определённых таблиц путём размещения соответствующих записей в таблице mysql.ndb_replication (см. Таблица ndb_replication).

    Примечание

    Если вы реплицируете таблицы с очень большими столбцами (такими как TEXT или BLOB столбцы), --ndb-log-updated-only также может быть полезен для уменьшения размера двоичных журналов и предотвращения возможных сбоев репликации из-за превышения max_allowed_packet.

    См. Раздел 19.5.1.21, «Репликация и max_allowed_packet» для получения дополнительной информации об этой проблеме.

  • На реплике вы должны определить, какой тип разрешения конфликтов применять (“побеждает последнее значение марки времени”, “побеждает одинаковое значение марки времени”, “побеждает первичный источник”, “побеждает первичный источник, полная транзакция” или ни один). Это выполняется с помощью системной таблицы mysql.ndb_replication и относится к одной или нескольким определённым таблицам (см. Таблица ndb_replication).

  • NDB Cluster также поддерживает обнаружение конфликтов при чтении, то есть обнаружение конфликтов между чтениями данной строки в одном кластере и обновлениями или удалениями той же строки в другом кластере. Это требует эксклюзивных блокировок при чтении, полученных путём установки ndb_log_exclusive_reads равной 1 на реплике. Все строки, прочитанные во время конфликтующего чтения, регистрируются в таблице исключений. Дополнительную информацию см. в разделе Обнаружение и разрешение конфликтов при чтении.

  • При использовании NDB$MAX_INS() или NDB$MAX_DEL_WIN_INS(), NDB может применять WRITE_ROW события идемпотентно, сопоставляя такое событие с вставкой, когда входящая строка ещё не существует, или с обновлением, если она существует.

    При использовании любой функции разрешения конфликтов, отличной от NDB$MAX_INS() или NDB$MAX_DEL_WIN_INS(), входящее запись всегда отклоняется, если строка уже существует.

При использовании функций NDB$OLD(), NDB$MAX(), NDB$MAX_DELETE_WIN(), NDB$MAX_INS() и NDB$MAX_DEL_WIN_INS() для разрешения конфликтов на основе марки времени, мы часто называем столбец, используемый для определения обновлений, столбцом “марки времени”. Однако тип данных этого столбца никогда не является TIMESTAMP; вместо этого его тип данных должен быть INT (INTEGER) или BIGINT. Столбец “марки времени” также должен быть UNSIGNED и NOT NULL.

Функции NDB$EPOCH() и NDB$EPOCH_TRANS(), обсуждаемые далее в этом разделе, работают путём сравнения относительного порядка эпох репликации, применяемых на первичном и вторичном NDB Cluster, и не используют марки времени.

Управление столбцом источника

Мы можем рассматривать операции обновления с точки зрения “до” и “после” изображений—то есть состояний таблицы до и после применения обновления. Обычно при обновлении таблицы с первичным ключом изображение “до” не представляет большого интереса; однако, когда нам нужно на основе каждого обновления определить, использовать ли обновлённые значения на реплике, мы должны убедиться, что оба изображения записаны в двоичный журнал источника. Это выполняется с помощью параметра --ndb-log-update-as-write для mysqld, как описано далее в этом разделе.

Важно

Способ логирования, полных строк или только обновлённых столбцов, определяется при запуске сервера MySQL и не может быть изменён в режиме онлайн; вы должны либо перезапустить mysqld, либо запустить новую экземпляр mysqld с другими параметрами логирования.

Настройка разрешения конфликтов

Разрешение конфликтов обычно включается на сервере, где могут возникнуть конфликты. Как и при выборе метода логирования, оно включается записями в таблице mysql.ndb_replication.

NBT_UPDATED_ONLY_MINIMAL и NBT_UPDATED_FULL_MINIMAL могут использоваться с NDB$EPOCH(), NDB$EPOCH2() и NDB$EPOCH_TRANS(), потому что для них не нужны значения “до” столбцов, которые не являются первичными ключами. Алгоритмы разрешения конфликтов, требующие старых значений, такие как NDB$MAX() и NDB$OLD(), не работают правильно со значениями binlog_type.

Функции разрешения конфликтов

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

  • NDB$OLD()

  • NDB$MAX()

  • NDB$MAX_DELETE_WIN()

  • NDB$MAX_INS()

  • NDB$MAX_DEL_WIN_INS()

  • NDB$EPOCH()

  • NDB$EPOCH_TRANS()

  • NDB$EPOCH2()

  • NDB$EPOCH2_TRANS()

NDB$OLD()

Если значение column_name одинаково как на источнике, так и на реплике, то обновление применяется; в противном случае обновление не применяется на реплике, и в журнал записывается исключение. Это иллюстрируется следующим псевдокодом:

if (source_old_column_value == replica_current_column_value)
  apply_update();
else
  log_exception();

Эта функция может использоваться для разрешения конфликтов по принципу “выигрывает одинаковое значение”. Этот тип разрешения конфликтов гарантирует, что обновления не применяются на реплике из неправильного источника.

Важно

Эта функция использует значение столбца из “предыдущего” образа источника.

NDB$MAX()

Для операции обновления или удаления, если значение столбца “timestamp” для данной строки, поступающее от источника, выше, чем на реплике, оно применяется; в противном случае оно не применяется на реплике. Это иллюстрируется следующим псевдокодом:

if (source_new_column_value > replica_current_column_value)
  apply_update();

Эта функция может использоваться для разрешения конфликтов по принципу “выигрывает наибольшая метка времени”. Этот тип разрешения конфликтов гарантирует, что в случае конфликта сохраняется версия строки, которая была обновлена последней.

Эта функция не оказывает влияния на конфликты между операциями записи, за исключением того, что операция записи с тем же первичным ключом, что и предыдущая операция записи, всегда отклоняется; она принимается и применяется только в том случае, если операция записи с тем же первичным ключом ещё не существует. Вы можете использовать NDB$MAX_INS() для обработки разрешения конфликтов между операциями записи.

Важно

Эта функция использует значение столбца из “последующего” образа источника.

NDB$MAX_DELETE_WIN()

Это вариация на тему NDB$MAX(). Из-за того, что для операции удаления метка времени недоступна, удаление с использованием NDB$MAX() фактически обрабатывается как NDB$OLD, но для некоторых вариантов использования это не оптимально. Для NDB$MAX_DELETE_WIN(), если значение столбца “timestamp” для данной строки, добавляющей или обновляющей существующую строку, поступающее от источника, выше, чем на реплике, оно применяется. Однако операции удаления всегда рассматриваются как имеющие более высокое значение. Это иллюстрируется следующим псевдокодом:

if ( (source_new_column_value > replica_current_column_value)
        ||
      operation.type == "delete")
  apply_update();

Эта функция может использоваться для разрешения конфликтов по принципу “наибольшая метка времени, выигрывает удаление”. Этот тип разрешения конфликтов гарантирует, что в случае конфликта сохраняется версия строки, которая была удалена или (иначе) обновлена последней.

Примечание

Как и в случае с NDB$MAX(), значение столбца из “последующего” образа источника является значением, используемым этой функцией.

NDB$MAX_INS()

Эта функция обеспечивает поддержку разрешения конфликтующих операций записи. Такие конфликты обрабатываются “NDB$MAX_INS()” следующим образом:

  1. Если нет конфликтующей записи, примените эту (это то же самое, что и NDB$MAX()).

  2. В противном случае примените разрешение конфликтов по принципу “выигрывает наибольшая метка времени”, следующим образом:

    1. Если метка времени для входящей записи больше, чем у конфликтующей записи, примените входящую операцию.

    2. Если метка времени для входящей записи не больше, отклоните входящую операцию записи.

При обработке операции вставки NDB$MAX_INS() сравнивает метки времени из источника и реплики, как показано в следующем псевдокоде:

if (source_new_column_value > replica_current_column_value)
  apply_insert();
else
  log_exception();

Для операции обновления обновлённое значение столбца метки времени из источника сравнивается со значением столбца метки времени реплики, как показано здесь:

if (source_new_column_value > replica_current_column_value)
  apply_update();
else
  log_exception();

Это то же самое, что и выполняется NDB$MAX().

Для операций удаления обработка также такая же, как и та, которая выполняется NDB$MAX() (и, следовательно, такая же, как NDB$OLD()), и делается так:

if (source_new_column_value == replica_current_column_value)
  apply_delete();
else
  log_exception();
NDB$MAX_DEL_WIN_INS()

Эта функция обеспечивает поддержку разрешения конфликтующих операций записи, а также разрешение “выигрывает удаление”, как и у NDB$MAX_DELETE_WIN(). Конфликты записи обрабатываются NDB$MAX_DEL_WIN_INS(), как показано здесь:

  1. Если нет конфликтующей записи, примените эту (это то же самое, что и NDB$MAX_DELETE_WIN()).

  2. В противном случае примените разрешение конфликтов по принципу “выигрывает наибольшая метка времени”, следующим образом:

    1. Если метка времени для входящей записи больше, чем у конфликтующей записи, примените входящую операцию.

    2. Если метка времени для входящей записи не больше, отклоните входящую операцию записи.

Обработку операций вставки, выполняемых NDB$MAX_DEL_WIN_INS(), можно представить в псевдокоде, как показано здесь:

if (source_new_column_value > replica_current_column_value)
  apply_insert();
else
  log_exception();

Для операций обновления обновлённое значение столбца метки времени из источника сравнивается со значением столбца метки времени реплики, вот так (снова используя псевдокод):

if (source_new_column_value > replica_current_column_value)
  apply_update();
else
  log_exception();

Удаления обрабатываются с использованием стратегии “всегда выигрывает удаление” (такой же, как NDB$MAX_DELETE_WIN()); DELETE всегда применяется без учёта каких-либо значений меток времени, как показано в этом псевдокоде:

if (operation.type == "delete")
  apply_delete();

Для конфликтов между операциями обновления и удаления эта функция ведет себя идентично NDB$MAX_DELETE_WIN().

NDB$EPOCH()

Функция NDB$EPOCH() отслеживает порядок, в котором реплицированные эпохи применяются в кластере реплик относительно изменений, происходящих на реплике. Этот относительный порядок используется для определения того, являются ли изменения, происходящие на реплике, одновременными с любыми изменениями, которые происходят локально, и поэтому потенциально находятся в конфликте.

Большая часть того, что следует в описании NDB$EPOCH(), также относится к NDB$EPOCH_TRANS(). Любые исключения указаны в тексте.

NDB$EPOCH() является асимметричной, работающей на одном кластере NDB в конфигурации двунаправленной репликации (иногда называемой репликацией “активный-активный”). Мы называем здесь кластер, на котором она работает, основным, а другой — вторичным. Реплика на основном кластере отвечает за обнаружение и обработку конфликтов, в то время как реплика на вторичном кластере не участвует ни в каком обнаружении или обработке конфликтов.

Когда реплика на основном кластере обнаруживает конфликты, она вставляет события в свой собственный двоичный журнал, чтобы компенсировать их; это гарантирует, что вторичный кластер NDB в конечном итоге снова выравнивается с основным, и поэтому предотвращает расхождение основного и вторичного кластеров. Этот механизм компенсации и выравнивания требует, чтобы основной кластер NDB всегда выигрывал любые конфликты со вторичным — то есть, что изменения основного кластера всегда используются вместо изменений со вторичного в случае конфликта. Это правило “всегда выигрывает основной” имеет следующие последствия:

  • Операции, изменяющие данные, после их фиксации на первичном узле, полностью сохраняются и не отменяются или не откатываются механизмом обнаружения и разрешения конфликтов.

  • Данные, считанные с первичного узла, полностью согласованы. Любые изменения, зафиксированные на первичном узле (локально или с реплики), не отменяются позднее.

  • Операции, изменяющие данные на вторичном узле, могут быть позднее отменены, если первичный узел определит, что они находятся в конфликте.

  • Отдельные строки, считанные на вторичном узле, всегда самосогласованы, каждая строка всегда отражает состояние, зафиксированное либо вторичным, либо первичным узлом.

  • Наборы строк, считанные на вторичном узле, могут не обязательно быть согласованными в данный момент времени. Для NDB$EPOCH_TRANS() это временное состояние; для NDB$EPOCH() это может быть постоянное состояние.

  • При условии достаточного периода без конфликтов все данные на вторичном кластере NDB (в конечном итоге) согласуются с данными первичного узла.

NDB$EPOCH() и NDB$EPOCH_TRANS() не требуют никаких изменений схемы пользователя или приложений для обнаружения конфликтов. Однако необходимо тщательно продумать используемую схему и шаблоны доступа, чтобы убедиться, что вся система функционирует в заданных пределах.

Каждая из функций NDB$EPOCH() и NDB$EPOCH_TRANS() может принимать необязательный параметр; это количество битов, используемых для представления младших 32 битов эпохи, и должно быть не меньше значения, рассчитанного, как показано здесь:

CEIL( LOG2( TimeBetweenGlobalCheckpoints / TimeBetweenEpochs ), 1)

Для значений по умолчанию этих параметров конфигурации (2000 и 100 миллисекунд соответственно) это дает значение 5 битов, поэтому значение по умолчанию (6) должно быть достаточным, если не используются другие значения для TimeBetweenGlobalCheckpoints, TimeBetweenEpochs или обоих. Слишком маленькое значение может привести к ложным срабатываниям, а слишком большое — к чрезмерному расходу места в базе данных.

Обе функции NDB$EPOCH() и NDB$EPOCH_TRANS() вставляют записи для конфликтующих строк в соответствующие таблицы исключений, при условии, что эти таблицы определены в соответствии с теми же правилами схемы таблиц исключений, что описаны в другом разделе (см. NDB$OLD()). Вы должны создать таблицу исключений перед созданием таблицы данных, с которой она будет использоваться.

Как и другие функции обнаружения конфликтов, обсуждаемые в этом разделе, функции NDB$EPOCH() и NDB$EPOCH_TRANS() активируются путем включения соответствующих записей в таблицу mysql.ndb_replication (см. таблицу ndb_replication). Роли первичного и вторичного кластеров NDB в этом сценарии полностью определяются записями в таблице mysql.ndb_replication.

Поскольку алгоритмы обнаружения конфликтов, используемые функциями NDB$EPOCH() и NDB$EPOCH_TRANS(), асимметричны, вы должны использовать разные значения для записей server_id первичной и вторичной реплик.

Конфликт только операций DELETE недостаточно для активации конфликта с помощью функций NDB$EPOCH() или NDB$EPOCH_TRANS(), и относительное расположение в эпохах не имеет значения.

Ограничения NDB$EPOCH()

Следующие ограничения в настоящее время применяются при использовании функции NDB$EPOCH() для обнаружения конфликтов:

  • Конфликты обнаруживаются с использованием границ эпохи кластера NDB с разрешением, пропорциональным TimeBetweenEpochs (по умолчанию: 100 миллисекунд). Минимальное окно конфликта — это минимальное время, в течение которого одновременные обновления одних и тех же данных на обоих кластерах всегда приводят к конфликту. Это всегда ненулевой промежуток времени, и он примерно пропорционален 2 * (latency + queueing + TimeBetweenEpochs). Это означает, что — принимая во внимание значение по умолчанию для TimeBetweenEpochs и игнорируя любые задержки между кластерами (а также любые задержки в очередях) — минимальный размер окна конфликта приблизительно равен 200 миллисекундам. Этот минимальный интервал следует учитывать при анализе ожидаемых шаблонов «гонок» в приложении.

  • Для таблиц, использующих функции NDB$EPOCH() и NDB$EPOCH_TRANS(), требуется дополнительное хранилище; требуется от 1 до 32 битов дополнительного пространства на строку в зависимости от значения, переданного функции.

  • Конфликты между операциями удаления могут привести к расхождению между первичным и вторичным узлом. Когда строка удаляется на обоих кластерах одновременно, конфликт может быть обнаружен, но не регистрируется, так как строка удалена. Это означает, что дальнейшие конфликты во время распространения любых последующих операций выравнивания не обнаруживаются, что может привести к расхождению.

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

  • В настоящее время поддерживаются только два кластера NDB в двунаправленной конфигурации «активно-активно» при использовании функций NDB$EPOCH() или NDB$EPOCH_TRANS() для обнаружения конфликтов.

  • Таблицы со столбцами типа BLOB или TEXT в настоящее время не поддерживаются с функциями NDB$EPOCH() или NDB$EPOCH_TRANS().

NDB$EPOCH_TRANS()

NDB$EPOCH_TRANS() расширяет функцию NDB$EPOCH(). Конфликты обнаруживаются и обрабатываются аналогично, используя правило «первичный узел выигрывает во всем» (см. NDB$EPOCH()), но с дополнительным условием, что все другие строки, обновленные в той же транзакции, в которой произошел конфликт, также считаются находящимися в конфликте. Другими словами, где функция NDB$EPOCH() выравнивает отдельные конфликтующие строки на вторичном узле, функция NDB$EPOCH_TRANS() выравнивает конфликтующие транзакции.

Кроме того, все транзакции, которые, как можно обнаружить, зависят от конфликтующей транзакции, также считаются находящимися в конфликте, эти зависимости определяются содержимым двоичного журнала вторичного кластера. Поскольку двоичный журнал содержит только операции изменения данных (вставки, обновления и удаления), только перекрывающиеся изменения данных используются для определения зависимостей между транзакциями.

NDB$EPOCH_TRANS() подчиняется тем же условиям и ограничениям, что и NDB$EPOCH(), а также требует, чтобы все идентификаторы транзакций записывались в двоичный журнал вторичного кластера, используя --ndb-log-transaction-id установленным в значение ON. Это добавляет переменную нагрузку (до 13 байтов на строку).

См. NDB$EPOCH().

NDB$EPOCH2()

Функция NDB$EPOCH2() похожа на функцию NDB$EPOCH(), за исключением того, что NDB$EPOCH2() предоставляет обработку удаления-удаления с двунаправленной топологией репликации. В этом случае роли первичного и вторичного узла для двух источников назначаются путем установки системной переменной ndb_conflict_role в соответствующее значение на каждом источнике (обычно один для PRIMARY, SECONDARY). При выполнении этого действия изменения, внесенные вторичным узлом, отражаются первичным узлом обратно на вторичный, который затем условно их применяет.

NDB$EPOCH2_TRANS()

NDB$EPOCH2_TRANS() расширяет функцию NDB$EPOCH2(). Конфликты обнаруживаются и обрабатываются аналогично, и роли первичного и вторичного узла для реплицируемых кластеров назначаются, но с дополнительным условием, что все другие строки, обновленные в той же транзакции, в которой произошел конфликт, также считаются находящимися в конфликте. То есть, функция NDB$EPOCH2() выравнивает отдельные конфликтующие строки на вторичном узле, а функция NDB$EPOCH_TRANS() выравнивает конфликтующие транзакции.

Функции NDB$EPOCH() и NDB$EPOCH_TRANS() используют метаданные, указанные для каждой строки, каждой последней измененной эпохи, чтобы определить на первичном узле, является ли входящее скопированное изменение строки со вторичного узла одновременным с локально зафиксированным изменением; одновременные изменения считаются конфликтующими, с последующими обновлениями таблиц исключений и выравниванием вторичного узла. Проблема возникает, когда строка удаляется на первичном узле, так что последняя измененная эпоха больше недоступна для определения, конфликтуют ли какие-либо скопированные операции, что означает, что конфликтующие операции удаления не обнаруживаются. Это может привести к расхождению, примером может служить удаление на одном кластере, одновременное с удалением и вставкой на другом; вот почему операции удаления могут направляться только на один кластер при использовании функций NDB$EPOCH() и NDB$EPOCH_TRANS().

Функция NDB$EPOCH2() обходит эту проблему — сохранение информации об удаленных строках на ПЕРВИЧНОМ узле — путем игнорирования конфликтов удаление-удаление и избежания возможного расхождения. Это достигается путем отражения любой операции, успешно примененной и скопированной со вторичного узла обратно на вторичный узел. При возвращении на вторичный узел она может использоваться для повторного применения операции на вторичном узле, которая была удалена операцией, исходящей с первичного узла.

При использовании NDB$EPOCH2(), следует учитывать, что вторичный сервер применяет удаление из первичного, удаляя новую строку до ее восстановления отражённой операцией. Теоретически, последующая вставка или обновление на вторичном сервере конфликтуют с удалением из первичного, но в этом случае мы выбираем игнорировать это и разрешить вторичному серверу “победить”, в интересах предотвращения расхождения между кластерами. Другими словами, после удаления первичный сервер не обнаруживает конфликтов и вместо этого сразу принимает последующие изменения вторичного сервера. Из-за этого состояние вторичного сервера может возвращаться к нескольким предыдущим сохранённым состояниям по мере перехода к конечному (стабильному) состоянию, и некоторые из них могут быть видны.

Также следует знать, что отражение всех операций со вторичного сервера на первичный увеличивает размер журнала первичного сервера logbinary, а также требует большей пропускной способности, использования ЦП и ввода-вывода на диск.

Применение отражённых операций на вторичном сервере зависит от состояния целевой строки на вторичном сервере. Применяются ли отражённые изменения на вторичном сервере, можно отслеживать, проверяя переменные состояния Ndb_conflict_reflected_op_prepare_count и Ndb_conflict_reflected_op_discard_count или столбцы CONFLICT_REFLECTED_OP_PREPARE_COUNT и CONFLICT_REFLECTED_OP_DISCARD_COUNT таблицы ndb_replication_applier_status схемы Performance Schema. Количество применённых изменений просто является разницей между этими двумя значениями (обратите внимание, что Ndb_conflict_reflected_op_prepare_count всегда больше или равно Ndb_conflict_reflected_op_discard_count).

События применяются только в том случае, если оба следующих условия истинны:

  • Существование строки — то есть, её наличие или отсутствие — соответствует типу события. Для операций удаления и обновления строка должна уже существовать. Для операций вставки строка должна не существовать.

  • Строка была последней изменённой первичным сервером. Возможно, это изменение было выполнено путём выполнения отражённой операции.

Если оба этих условия не выполняются, вторичный сервер отбрасывает отражённую операцию.

Таблица исключений разрешения конфликтов

Для использования функции разрешения конфликтов NDB$OLD() также необходимо создать таблицу исключений, соответствующую каждой таблице NDB, для которой должен применяться этот тип разрешения конфликтов. Это также верно при использовании NDB$EPOCH() или NDB$EPOCH_TRANS(). Название этой таблицы совпадает с именем таблицы, к которой должно применяться разрешение конфликтов, с добавленной строкой $EX. (Например, если имя исходной таблицы — mytable, имя соответствующей таблицы исключений должно быть mytable$EX.) Синтаксис создания таблицы исключений представлен здесь:

CREATE TABLE original_table$EX  (
    [NDB$]server_id INT UNSIGNED,
    [NDB$]source_server_id INT UNSIGNED,
    [NDB$]source_epoch BIGINT UNSIGNED,
    [NDB$]count INT UNSIGNED,

    [NDB$OP_TYPE ENUM('WRITE_ROW','UPDATE_ROW', 'DELETE_ROW',
      'REFRESH_ROW', 'READ_ROW') NOT NULL,]
    [NDB$CFT_CAUSE ENUM('ROW_DOES_NOT_EXIST', 'ROW_ALREADY_EXISTS',
      'DATA_IN_CONFLICT', 'TRANS_IN_CONFLICT') NOT NULL,]
    [NDB$ORIG_TRANSID BIGINT UNSIGNED NOT NULL,]

    original_table_pk_columns,

    [orig_table_column|orig_table_column$OLD|orig_table_column$NEW,]

    [additional_columns,]

    PRIMARY KEY([NDB$]server_id, [NDB$]source_server_id, [NDB$]source_epoch, [NDB$]count)
) ENGINE=NDB;

Первые четыре столбца являются обязательными. Имена первых четырёх столбцов и столбцов, соответствующих столбцам первичного ключа исходной таблицы, не являются критическими; однако для ясности и согласованности рекомендуется использовать имена, показанные здесь, для столбцов server_id, source_server_id, source_epoch и count и те же имена, что и в исходной таблице, для столбцов, соответствующих столбцам первичного ключа исходной таблицы.

Если таблица исключений использует один или несколько необязательных столбцов NDB$OP_TYPE, NDB$CFT_CAUSE или NDB$ORIG_TRANSID, обсуждаемых позднее в этом разделе, то каждое из требуемых столбцов также должно быть названо с префиксом NDB$. При желании можно использовать префикс NDB$ для именования обязательных столбцов, даже если необязательные столбцы не определены, но в этом случае все четыре обязательных столбца должны быть названы с использованием префикса.

После этих столбцов должны быть скопированы столбцы, составляющие первичный ключ исходной таблицы, в порядке, в котором они используются для определения первичного ключа исходной таблицы. Типы данных для столбцов, дублирующих столбцы первичного ключа исходной таблицы, должны быть такими же (или больше) как у исходных столбцов. Может быть использована подмножество столбцов первичного ключа.

Таблица исключений должна использовать движок хранения NDB. (Пример использования NDB$OLD() с таблицей исключений показан позже в этом разделе.)

Дополнительные столбцы могут быть необязательно определены после скопированных столбцов первичного ключа, но не перед ними; любые такие дополнительные столбцы не могут быть NOT NULL. NDB Cluster поддерживает три дополнительных предопределённых необязательных столбца NDB$OP_TYPE, NDB$CFT_CAUSE и NDB$ORIG_TRANSID, которые описаны в следующих абзацах.

NDB$OP_TYPE: Этот столбец может быть использован для получения типа операции, вызвавшей конфликт. Если вы используете этот столбец, определите его так:

NDB$OP_TYPE ENUM('WRITE_ROW', 'UPDATE_ROW', 'DELETE_ROW',
    'REFRESH_ROW', 'READ_ROW') NOT NULL

Типы операций WRITE_ROW, UPDATE_ROW и DELETE_ROW представляют операции, инициированные пользователем. Операции REFRESH_ROW — это операции, сгенерированные разрешением конфликтов в компенсирующих транзакциях, отправленных обратно в исходный кластер из кластера, обнаружившего конфликт. Операции READ_ROW — это операции отслеживания чтения, инициированные пользователем, определённые с эксклюзивными блокировками строк.

NDB$CFT_CAUSE: Вы можете определить необязательный столбец NDB$CFT_CAUSE, который предоставляет причину зарегистрированного конфликта. Если этот столбец используется, он определяется так:

NDB$CFT_CAUSE ENUM('ROW_DOES_NOT_EXIST', 'ROW_ALREADY_EXISTS',
    'DATA_IN_CONFLICT', 'TRANS_IN_CONFLICT') NOT NULL

ROW_DOES_NOT_EXIST может быть указан как причина для операций UPDATE_ROW и WRITE_ROW; ROW_ALREADY_EXISTS может быть указан для событий WRITE_ROW. DATA_IN_CONFLICT указывается, когда функция конфликта по строкам обнаруживает конфликт; TRANS_IN_CONFLICT указывается, когда функция конфликта транзакций отклоняет все операции, относящиеся к полной транзакции.

NDB$ORIG_TRANSID: Столбец NDB$ORIG_TRANSID, если используется, содержит идентификатор исходной транзакции. Этот столбец должен быть определён так:

NDB$ORIG_TRANSID BIGINT UNSIGNED NOT NULL

NDB$ORIG_TRANSID — это 64-битное значение, сгенерированное NDB. Это значение может быть использовано для сопоставления нескольких записей таблицы исключений, относящихся к одной и той же конфликтной транзакции из одной или разных таблиц исключений.

Дополнительные столбцы-ссылки, которые не являются частью первичного ключа исходной таблицы, могут быть названы colname$OLD или colname$NEW. colname$OLD ссылается на старые значения в операциях обновления и удаления — то есть, операциях, содержащих события DELETE_ROW. colname$NEW может использоваться для ссылки на новые значения в операциях вставки и обновления — другими словами, операциях, использующих события WRITE_ROW, события UPDATE_ROW или оба типа событий. Если конфликтная операция не предоставляет значения для заданного столбца-ссылки, который не является первичным ключом, строка таблицы исключений содержит либо NULL, либо определённое значение по умолчанию для этого столбца.

Важно

Таблица mysql.ndb_replication читается, когда таблица данных настраивается для репликации, поэтому строка, соответствующая таблице, подлежащей репликации, должна быть вставлена в mysql.ndb_replication до создания подлежащей репликации таблицы.

Переменные состояния обнаружения конфликтов

Несколько переменных состояния могут использоваться для мониторинга обнаружения конфликтов. Вы можете увидеть, сколько строк было обнаружено в конфликте с момента последней перезагрузки этой реплики из текущего значения системной переменной состояния Ndb_conflict_fn_epoch, или проверив столбец CONFLICT_FN_EPOCH таблицы Performance Schema ndb_replication_applier_status.

Ndb_conflict_fn_epoch_trans предоставляет количество строк, которые были непосредственно обнаружены в конфликте NDB$EPOCH_TRANS(). Ndb_conflict_fn_epoch2 и Ndb_conflict_fn_epoch2_trans отображают количество строк, обнаруженных в конфликте, соответственно, NDB$EPOCH2() и NDB$EPOCH2_TRANS(). Количество строк, фактически перестроенных, включая те, которые затронуты из-за их членства в или зависимости от тех же транзакций, что и другие конфликтующие строки, указывается переменной Ndb_conflict_trans_row_reject_count.

Другая системная переменная состояния сервера Ndb_conflict_fn_max предоставляет подсчет числа случаев, когда строка не была применена на текущем узле SQL из-за конфликта “выигрывает наибольшая метка времени” с момента последнего запуска mysqld. Ndb_conflict_fn_max_del_win предоставляет подсчет числа случаев, когда разрешение конфликтов, основанное на результате NDB$MAX_DELETE_WIN(), было применено.

Ndb_conflict_fn_max_ins отслеживает число случаев, когда обработка “выигрывает наибольшая метка времени” была применена к операциям записи (используя NDB$MAX_INS()); подсчет числа случаев, когда обработка “выигрывает та же метка времени” операций записи была применена (как реализовано NDB$MAX_DEL_WIN_INS()), предоставляется переменной состояния Ndb_conflict_fn_max_del_win_ins.

Количество случаев, когда строка не была применена в результате разрешения конфликтов “выигрывает та же метка времени” на данном узле mysqld с момента последней перезагрузки, задается глобальной переменной состояния Ndb_conflict_fn_old. В дополнение к увеличению Ndb_conflict_fn_old, первичный ключ строки, которая не была использована, вставляется в таблицу исключений, как описано в другом месте в этом разделе.

Каждая из переменных состояния, упомянутых в предыдущих абзацах, имеет эквивалентный столбец в таблице Performance Schema ndb_replication_applier_status. Более подробная информация представлена в описании этой таблицы. См. также Раздел 25.4.3.9.3, «Переменные состояния кластера NDB».

Примеры

В следующих примерах предполагается, что у вас уже настроено реплицирование кластера NDB, как описано в разделе 25.7.5 «Подготовка кластера NDB к реплицированию» и разделе 25.7.6 «Запуск реплицирования кластера NDB (один канал реплицирования)».

Пример NDB$MAX(). Предположим, вы хотите включить механизм разрешения конфликтов «timestamp с наибольшим значением» для таблицы test.t1, используя столбец mycol в качестве «timestamp». Это можно сделать следующими шагами:

  1. Убедитесь, что вы запустили исходный mysqld с --ndb-log-update-as-write=OFF.

  2. На источнике выполните эту INSERT команду:

    INSERT INTO mysql.ndb_replication
        VALUES ('test', 't1', 0, NULL, 'NDB$MAX(mycol)');
    
    Примечание

    Если таблица ndb_replication еще не существует, вам необходимо создать её. См. таблицу ndb_replication.

    Вставка 0 в столбец server_id указывает, что все узлы SQL, обращающиеся к этой таблице, должны использовать разрешение конфликтов. Если вы хотите использовать разрешение конфликтов только для конкретного mysqld, используйте фактический идентификатор сервера.

    Вставка NULL в столбец binlog_type имеет тот же эффект, что и вставка 0 (NBT_DEFAULT); используется значение по умолчанию сервера.

  3. Создайте таблицу test.t1:

    CREATE TABLE test.t1 (
        columns
        mycol INT UNSIGNED,
        columns
    ) ENGINE=NDB;
    

    Теперь, когда обновления выполняются для этой таблицы, применяется разрешение конфликтов, и версия строки с наибольшим значением для mycol записывается на реплику.

Примечание

Другие binlog_type опции, такие как NBT_UPDATED_ONLY_USE_UPDATE (6), должны использоваться для управления ведением журнала на источнике, используя таблицу ndb_replication, а не параметры командной строки.

Пример NDB$OLD(). Предположим, что реплицируется таблица NDB (определённая здесь), и вы хотите включить механизм разрешения конфликтов «timestamp совпадает» для обновлений в этой таблице:

CREATE TABLE test.t2  (
    a INT UNSIGNED NOT NULL,
    b CHAR(25) NOT NULL,
    columns,
    mycol INT UNSIGNED NOT NULL,
    columns,
    PRIMARY KEY pk (a, b)
)   ENGINE=NDB;

Необходимы следующие шаги в указанном порядке:

  1. Сначала—и до создания test.t2—вы должны вставить строку в таблицу mysql.ndb_replication, как показано здесь:

    INSERT INTO mysql.ndb_replication
        VALUES ('test', 't2', 0, 0, 'NDB$OLD(mycol)');
    

    Возможные значения для столбца binlog_type показаны ранее в этом разделе; в данном случае мы используем 0, чтобы указать использование поведения ведения журнала по умолчанию сервера. Значение 'NDB$OLD(mycol)' должно быть вставлено в столбец conflict_fn.

  2. Создайте соответствующую таблицу исключений для test.t2. Приведенное здесь утверждение создания таблицы включает все необходимые столбцы; любые дополнительные столбцы должны быть объявлены после этих столбцов и перед определением первичного ключа таблицы.

    CREATE TABLE test.t2$EX  (
        server_id INT UNSIGNED,
        source_server_id INT UNSIGNED,
        source_epoch BIGINT UNSIGNED,
        count INT UNSIGNED,
        a INT UNSIGNED NOT NULL,
        b CHAR(25) NOT NULL,
    
        [additional_columns,]
    
        PRIMARY KEY(server_id, source_server_id, source_epoch, count)
    )   ENGINE=NDB;
    

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

    CREATE TABLE test.t2$EX  (
        NDB$server_id INT UNSIGNED,
        NDB$source_server_id INT UNSIGNED,
        NDB$source_epoch BIGINT UNSIGNED,
        NDB$count INT UNSIGNED,
        a INT UNSIGNED NOT NULL,
    
        NDB$OP_TYPE ENUM('WRITE_ROW','UPDATE_ROW', 'DELETE_ROW',
          'REFRESH_ROW', 'READ_ROW') NOT NULL,
        NDB$CFT_CAUSE ENUM('ROW_DOES_NOT_EXIST', 'ROW_ALREADY_EXISTS',
          'DATA_IN_CONFLICT', 'TRANS_IN_CONFLICT') NOT NULL,
        NDB$ORIG_TRANSID BIGINT UNSIGNED NOT NULL,
    
        [additional_columns,]
    
        PRIMARY KEY(NDB$server_id, NDB$source_server_id, NDB$source_epoch, NDB$count)
    )   ENGINE=NDB;
    
    Примечание

    Префикс NDB$ необходим для четырёх обязательных столбцов, так как мы включили хотя бы один из столбцов NDB$OP_TYPE, NDB$CFT_CAUSE или NDB$ORIG_TRANSID в определении таблицы.

  3. Создайте таблицу test.t2, как показано ранее.

Эти шаги должны выполняться для каждой таблицы, для которой вы хотите выполнить разрешение конфликтов, используя NDB$OLD(). Для каждой такой таблицы должна быть соответствующая строка в mysql.ndb_replication, и должна быть таблица исключений в той же базе данных, что и реплицируемая таблица.

Обнаружение и разрешение конфликтов при чтении. Кластер NDB также поддерживает отслеживание операций чтения, что позволяет управлять конфликтами между чтением строки в одном кластере и обновлением или удалением этой же строки в другом в сценариях циклической репликации. В этом примере таблицы employee и department моделируют ситуацию, когда сотрудник переводится из одного отдела в другой в исходном кластере (который далее обозначается как кластер A), в то время как реплицирующий кластер (B) обновляет количество сотрудников в прежнем отделе сотрудника в вложенной транзакции.

Таблицы данных были созданы с помощью следующих SQL-запросов:

# Employee table
CREATE TABLE employee (
    id INT PRIMARY KEY,
    name VARCHAR(2000),
    dept INT NOT NULL
)   ENGINE=NDB;

# Department table
CREATE TABLE department (
    id INT PRIMARY KEY,
    name VARCHAR(2000),
    members INT
)   ENGINE=NDB;

Содержимое двух таблиц включает строки, показанные в (частичном) выводе следующих SELECT команд:

mysql> SELECT id, name, dept FROM employee;
+---------------+------+
| id   | name   | dept |
+------+--------+------+
...
| 998  |  Mike  | 3    |
| 999  |  Joe   | 3    |
| 1000 |  Mary  | 3    |
...
+------+--------+------+

mysql> SELECT id, name, members FROM department;
+-----+-------------+---------+
| id  | name        | members |
+-----+-------------+---------+
...
| 3   | Old project | 24      |
...
+-----+-------------+---------+

Предполагается, что мы уже используем таблицу исключений, которая включает четыре необходимых столбца (и они используются для первичного ключа этой таблицы), необязательные столбцы для типа операции и причины, а также столбец первичного ключа исходной таблицы, созданный с помощью показанного здесь SQL-запроса:

CREATE TABLE employee$EX  (
    NDB$server_id INT UNSIGNED,
    NDB$source_server_id INT UNSIGNED,
    NDB$source_epoch BIGINT UNSIGNED,
    NDB$count INT UNSIGNED,

    NDB$OP_TYPE ENUM( 'WRITE_ROW','UPDATE_ROW', 'DELETE_ROW',
                      'REFRESH_ROW','READ_ROW') NOT NULL,
    NDB$CFT_CAUSE ENUM( 'ROW_DOES_NOT_EXIST',
                        'ROW_ALREADY_EXISTS',
                        'DATA_IN_CONFLICT',
                        'TRANS_IN_CONFLICT') NOT NULL,

    id INT NOT NULL,

    PRIMARY KEY(NDB$server_id, NDB$source_server_id, NDB$source_epoch, NDB$count)
)   ENGINE=NDB;

Предположим, что одновременно произошли две транзакции на двух кластерах. В кластере A мы создаём новый отдел, затем перемещаем сотрудника с номером 999 в этот отдел, используя следующие SQL-запросы:

BEGIN;
  INSERT INTO department VALUES (4, "New project", 1);
  UPDATE employee SET dept = 4 WHERE id = 999;
COMMIT;

В то же время, в кластере B, другая транзакция читает из employee, как показано здесь:

BEGIN;
  SELECT name FROM employee WHERE id = 999;
  UPDATE department SET members = members - 1  WHERE id = 3;
commit;

Механизм разрешения конфликтов обычно не обнаруживает конфликтующие транзакции, так как конфликт происходит между операцией чтения (SELECT) и операцией обновления. Вы можете обойти эту проблему, выполнив SET ndb_log_exclusive_reads = 1 на реплицирующем кластере. Получение эксклюзивных блокировок чтения таким образом приводит к тому, что любые строки, прочитанные на источнике, помечаются как требующие разрешения конфликтов на реплицирующем кластере. Если мы включим эксклюзивные чтения таким образом до регистрации этих транзакций, чтение в кластере B отслеживается и отправляется в кластер A для разрешения; конфликт в строке сотрудника обнаруживается, а транзакция в кластере B прерывается.

Конфликт регистрируется в таблице исключений (в кластере A) как операция READ_ROW (см. Таблицу исключений разрешения конфликтов для описания типов операций), как показано здесь:

mysql> SELECT id, NDB$OP_TYPE, NDB$CFT_CAUSE FROM employee$EX;
+-------+-------------+-------------------+
| id    | NDB$OP_TYPE | NDB$CFT_CAUSE     |
+-------+-------------+-------------------+
...
| 999   | READ_ROW    | TRANS_IN_CONFLICT |
+-------+-------------+-------------------+

Любые обнаруженные строки в операции чтения помечаются. Это означает, что несколько строк, полученных в результате одного конфликта, могут быть зарегистрированы в таблице исключений, как показано при исследовании эффектов конфликта между обновлением в кластере A и чтением нескольких строк в кластере B из одной таблицы в одновременных транзакциях. Транзакция, выполненная в кластере A, показана здесь:

BEGIN;
  INSERT INTO department VALUES (4, "New project", 0);
  UPDATE employee SET dept = 4 WHERE dept = 3;
  SELECT COUNT(*) INTO @count FROM employee WHERE dept = 4;
  UPDATE department SET members = @count WHERE id = 4;
COMMIT;

Одновременно в кластере B выполняется транзакция, содержащая показанные здесь команды:

SET ndb_log_exclusive_reads = 1;  # Must be set if not already enabled
...
BEGIN;
  SELECT COUNT(*) INTO @count FROM employee WHERE dept = 3 FOR UPDATE;
  UPDATE department SET members = @count WHERE id = 3;
COMMIT;

В этом случае все три строки, соответствующие условию WHERE во второй транзакции, SELECT, читаются, и поэтому помечаются в таблице исключений, как показано здесь:

mysql> SELECT id, NDB$OP_TYPE, NDB$CFT_CAUSE FROM employee$EX;
+-------+-------------+-------------------+
| id    | NDB$OP_TYPE | NDB$CFT_CAUSE     |
+-------+-------------+-------------------+
...
| 998   | READ_ROW    | TRANS_IN_CONFLICT |
| 999   | READ_ROW    | TRANS_IN_CONFLICT |
| 1000  | READ_ROW    | TRANS_IN_CONFLICT |
...
+-------+-------------+-------------------+

Отслеживание чтения выполняется только на основе существующих строк. Чтение, основанное на заданном условии, отслеживает конфликты только тех строк, которые найдены, а не тех, которые вставлены в вложенную транзакцию. Это похоже на то, как выполняется эксклюзивная блокировка строк в одном экземпляре кластера NDB.

Пример обнаружения и разрешения конфликтов при вставке. Следующий пример иллюстрирует использование функций обнаружения конфликтов при вставке. Предполагается, что мы реплицируем две таблицы t1 и t2 в базе данных test, и что мы хотим использовать обнаружение конфликтов при вставке с NDB$MAX_INS() для t1 и NDB$MAX_DEL_WIN_INS() для t2. Две таблицы данных создаются позже в процессе настройки.

Настройка разрешения конфликтов при вставке аналогична настройке других алгоритмов обнаружения и разрешения конфликтов, как показано в предыдущих примерах. Если таблица mysql.ndb_replication, используемая для конфигурирования бинарного ведения журнала и разрешения конфликтов, ещё не существует, сначала необходимо её создать, как показано здесь:

CREATE TABLE mysql.ndb_replication (
    db VARBINARY(63),
    table_name VARBINARY(63),
    server_id INT UNSIGNED,
    binlog_type INT UNSIGNED,
    conflict_fn VARBINARY(128),
    PRIMARY KEY USING HASH (db, table_name, server_id)
) ENGINE=NDB
PARTITION BY KEY(db,table_name);

Таблица ndb_replication действует на основе каждой таблицы; другими словами, нам нужно вставить строку, содержащую информацию о таблице, значение binlog_type, функцию разрешения конфликтов, которую следует использовать, и имя столбца отметки времени (X) для каждой настраиваемой таблицы, как показано ниже:

INSERT INTO mysql.ndb_replication VALUES ("test", "t1", 0, 7, "NDB$MAX_INS(X)");
INSERT INTO mysql.ndb_replication VALUES ("test", "t2", 0, 7, "NDB$MAX_DEL_WIN_INS(X)");

Здесь мы установили binlog_type как NBT_FULL_USE_UPDATE (7), что означает, что полные строки всегда регистрируются. См. таблицу ndb_replication для других возможных значений.

Вы также можете создать таблицу исключений, соответствующую каждой таблице NDB, для которой требуется разрешение конфликтов. Таблица исключений записывает все строки, отклоненные функцией разрешения конфликтов для заданной таблицы. Таблицы исключений для обнаружения конфликтов репликации для таблиц t1 и t2 могут быть созданы с помощью следующих двух операторов SQL:

CREATE TABLE `t1$EX` (
    NDB$server_id INT UNSIGNED,
    NDB$source_server_id INT UNSIGNED,
    NDB$source_epoch BIGINT UNSIGNED,
    NDB$count INT UNSIGNED,
    NDB$OP_TYPE ENUM('WRITE_ROW', 'UPDATE_ROW', 'DELETE_ROW',
                     'REFRESH_ROW', 'READ_ROW') NOT NULL,
    NDB$CFT_CAUSE ENUM('ROW_DOES_NOT_EXIST', 'ROW_ALREADY_EXISTS',
                       'DATA_IN_CONFLICT', 'TRANS_IN_CONFLICT') NOT NULL,
    a INT NOT NULL,
    PRIMARY KEY(NDB$server_id, NDB$source_server_id,
                NDB$source_epoch, NDB$count)
) ENGINE=NDB;

CREATE TABLE `t2$EX` (
    NDB$server_id INT UNSIGNED,
    NDB$source_server_id INT UNSIGNED,
    NDB$source_epoch BIGINT UNSIGNED,
    NDB$count INT UNSIGNED,
    NDB$OP_TYPE ENUM('WRITE_ROW', 'UPDATE_ROW', 'DELETE_ROW',
                     'REFRESH_ROW', 'READ_ROW') NOT NULL,
    NDB$CFT_CAUSE ENUM( 'ROW_DOES_NOT_EXIST', 'ROW_ALREADY_EXISTS',
                        'DATA_IN_CONFLICT', 'TRANS_IN_CONFLICT') NOT NULL,
    a INT NOT NULL,
    PRIMARY KEY(NDB$server_id, NDB$source_server_id,
                NDB$source_epoch, NDB$count)
) ENGINE=NDB;

Наконец, после создания только что показанных таблиц исключений, вы можете создать таблицы данных, которые должны быть реплицированы и контролироваться разрешением конфликтов, используя следующие два оператора SQL:

CREATE TABLE t1 (
    a INT PRIMARY KEY,
    b VARCHAR(32),
    X INT UNSIGNED
) ENGINE=NDB;

CREATE TABLE t2 (
    a INT PRIMARY KEY,
    b VARCHAR(32),
    X INT UNSIGNED
) ENGINE=NDB;

Для каждой таблицы столбец X используется в качестве столбца отметки времени.

После создания на источнике t1 и t2 реплицируются и предполагается, что они существуют как на источнике, так и на реплике. В оставшейся части этого примера мы используем mysqlS> для обозначения подключения клиента mysql к источнику и mysqlR> для обозначения клиента mysql, работающего на реплике.

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

mysqlS> INSERT INTO t1 VALUES (1, 'Initial X=1', 1);
Query OK, 1 row affected (0.01 sec)

mysqlS> INSERT INTO t2 VALUES (1, 'Initial X=1', 1);
Query OK, 1 row affected (0.01 sec)

Мы можем быть уверены, что эти две строки реплицируются без возникновения конфликтов, так как таблицы на реплике не содержали строк до выполнения операторов INSERT на источнике. Мы можем проверить это, выбрав из таблиц на реплике, как показано здесь:

mysqlR> TABLE t1 ORDER BY a;
+---+-------------+------+
| a | b           | X    |
+---+-------------+------+
| 1 | Initial X=1 |    1 |
+---+-------------+------+
1 row in set (0.00 sec)

mysqlR> TABLE t2 ORDER BY a;
+---+-------------+------+
| a | b           | X    |
+---+-------------+------+
| 1 | Initial X=1 |    1 |
+---+-------------+------+
1 row in set (0.00 sec)

Далее, мы вставляем новые строки в таблицы на реплике, как показано ниже:

mysqlR> INSERT INTO t1 VALUES (2, 'Replica X=2', 2);
Query OK, 1 row affected (0.01 sec)

mysqlR> INSERT INTO t2 VALUES (2, 'Replica X=2', 2);
Query OK, 1 row affected (0.01 sec)

Теперь мы вставляем конфликтующие строки в таблицы на источнике, имеющие большие значения столбца отметки времени (X), используя операторы, показанные здесь:

mysqlS> INSERT INTO t1 VALUES (2, 'Replica X=20', 20);
Query OK, 1 row affected (0.01 sec)

mysqlS> INSERT INTO t2 VALUES (2, 'Replica X=20', 20);
Query OK, 1 row affected (0.01 sec)

Теперь мы наблюдаем результаты, повторно выбирая (снова) из обеих таблиц на реплике, как показано здесь:

mysqlR> TABLE t1 ORDER BY a;
+---+-------------+-------+
| a | b           | X     |
+---+-------------+-------+
| 1 | Initial X=1 |    1  |
+---+-------------+-------+
| 2 | Source X=20 |   20  |
+---+-------------+-------+
2 rows in set (0.00 sec)

mysqlR> TABLE t2 ORDER BY a;
+---+-------------+-------+
| a | b           | X     |
+---+-------------+-------+
| 1 | Initial X=1 |    1  |
+---+-------------+-------+
| 1 | Source X=20 |   20  |
+---+-------------+-------+
2 rows in set (0.00 sec)

Строки, вставленные на источнике, имеющие значения отметки времени, большие, чем у конфликтующих строк на реплике, заменили эти строки. На реплике мы затем вставляем две новые строки, которые не конфликтуют ни с одной существующей строкой в t1 или t2, как показано ниже:

mysqlR> INSERT INTO t1 VALUES (3, 'Replica X=30', 30);
Query OK, 1 row affected (0.01 sec)

mysqlR> INSERT INTO t2 VALUES (3, 'Replica X=30', 30);
Query OK, 1 row affected (0.01 sec)

Вставка дополнительных строк на источнике с тем же значением первичного ключа (3) приводит к конфликтам, как и прежде, но на этот раз мы используем значение для столбца отметки времени, меньшее, чем в этом же столбце в конфликтующих строках на реплике.

mysqlS> INSERT INTO t1 VALUES (3, 'Source X=3', 3);
Query OK, 1 row affected (0.01 sec)

mysqlS> INSERT INTO t2 VALUES (3, 'Source X=3', 3);
Query OK, 1 row affected (0.01 sec)

Мы можем увидеть, что оба вставленных оператора с источника были отклонены репликой, и ранее вставленные строки на реплике не были перезаписаны, как показано здесь в клиенте mysql на реплике:

mysqlR> TABLE t1 ORDER BY a;
+---+--------------+-------+
| a | b            | X     |
+---+--------------+-------+
| 1 |  Initial X=1 |    1  |
+---+--------------+-------+
| 2 |  Source X=20 |   20  |
+---+--------------+-------+
| 3 | Replica X=30 |   30  |
+---+--------------+-------+
3 rows in set (0.00 sec)

mysqlR> TABLE t2 ORDER BY a;
+---+--------------+-------+
| a | b            | X     |
+---+--------------+-------+
| 1 |  Initial X=1 |    1  |
+---+--------------+-------+
| 2 |  Source X=20 |   20  |
+---+--------------+-------+
| 3 | Replica X=30 |   30  |
+---+--------------+-------+
3 rows in set (0.00 sec)

Вы можете увидеть информацию о строках, которые были отклонены, в таблицах исключений, как показано здесь:

mysqlR> SELECT  NDB$server_id, NDB$source_server_id, NDB$count,
      >         NDB$OP_TYPE, NDB$CFT_CAUSE, a
      > FROM t1$EX
      > ORDER BY NDB$count\G
*************************** 1. row ***************************
NDB$server_id       : 2
NDB$source_server_id: 1
NDB$count           : 1
NDB$OP_TYPE         : WRITE_ROW
NDB$CFT_CAUSE       : DATA_IN_CONFLICT
a                   : 3
1 row in set (0.00 sec)

mysqlR> SELECT  NDB$server_id, NDB$source_server_id, NDB$count,
      >         NDB$OP_TYPE, NDB$CFT_CAUSE, a
      > FROM t2$EX
      > ORDER BY NDB$count\G
*************************** 1. row ***************************
NDB$server_id       : 2
NDB$source_server_id: 1
NDB$count           : 1
NDB$OP_TYPE         : WRITE_ROW
NDB$CFT_CAUSE       : DATA_IN_CONFLICT
a                   : 3
1 row in set (0.00 sec)

Как мы видели ранее, никакие другие строки, вставленные на источнике, не были отклонены репликой, только те строки, у которых значение столбца отметки времени было меньше, чем у строк в конфликте на реплике.

© 2025 Oracle
Licensed under the GPLv2 License.
https://docs.oracle.com/cd/E17952_01/mysql-9.2-en/mysql-cluster-replication-conflict-resolution.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API