21.7.11 Разрешение конфликтов при репликации NDB Cluster
При использовании конфигурации репликации с несколькими источниками (включая циклическую репликацию) возможно, что разные источники попытаются обновить одну и ту же строку на реплике с разными данными. Разрешение конфликтов в NDB Cluster Replication предоставляет способ разрешения таких конфликтов, позволяя использовать определяемую пользователем колонку разрешения для определения того, следует ли применять обновление из данного источника на реплике.
Некоторые типы разрешения конфликтов, поддерживаемые NDB Cluster (NDB$OLD(), NDB$MAX(), NDB$MAX_DELETE_WIN()), реализуют эту колонку как колонку “временная метка” (хотя её тип не может быть 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.Дополнительная информация об этой проблеме представлена в разделе Раздел 16.4.1.19, «Репликация и max_allowed_packet».
На реплике необходимо определить, какой тип разрешения конфликтов следует применить (“последняя временная метка выигрывает”, “одинаковая временная метка выигрывает”, “первичный выигрывает”, “первичный выигрывает, полная транзакция” или ни один). Это делается с помощью таблицы системных переменных
mysql.ndb_replicationи применяется к одной или нескольким конкретным таблицам (см. Таблица ndb_replication).NDB Cluster также поддерживает обнаружение конфликтов при чтении, то есть обнаружение конфликтов между чтением данной строки в одном кластере и обновлением или удалением той же строки в другом кластере. Это требует эксклюзивных блокировок чтения, полученных путем установки
ndb_log_exclusive_readsравным 1 на реплике. Все строки, прочитанные при возникновении конфликта при чтении, регистрируются в таблице исключений. Более подробная информация представлена в разделе Обнаружение и разрешение конфликтов при чтении.NDBприменяетWRITE_ROWсобытия строго как вставки, требуя, чтобы такой строки не было уже; то есть входящая запись всегда отклоняется, если строка уже существует.
При использовании функций NDB$OLD(), NDB$MAX() и NDB$MAX_DELETE_WIN() для разрешения конфликтов на основе временных меток, мы часто называем колонку, используемую для определения обновлений, колонкой “временная метка”. Однако тип данных этой колонки никогда не является TIMESTAMP; вместо этого её тип данных должен быть INT (INTEGER) или BIGINT. Колонку “временная метка” также следует UNSIGNED и NOT NULL.
Функции NDB$EPOCH() и NDB$EPOCH_TRANS(), обсуждаемые ниже в этом разделе, работают путем сравнения относительного порядка эпох репликации, применяемых на первичном и вторичном NDB Cluster, и не используют временные метки.
Управление колонкой источника
Мы можем рассматривать операции обновления с точки зрения “до” и “после” изображений — то есть состояний таблицы до и после применения обновления. Обычно при обновлении таблицы с первичным ключом изображение “до” не представляет большого интереса; однако, когда нам нужно определить, следует ли использовать обновлённые значения на реплике на основе каждого обновления, нам нужно убедиться, что оба изображения записываются в двоичный журнал источника. Это делается с помощью опции --ndb-log-update-as-write для 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 Replication. Эти функции перечислены здесь в алфавитном порядке:
NDB$OLD()
Если значение column_name одинаково на источнике и реплике, то обновление применяется; в противном случае обновление не применяется на реплике, и об этом записывается исключение в журнал. Это иллюстрируется следующим псевдокодом:
if (source_old_column_value == replica_current_column_value)
apply_update();
else
log_exception();
Эта функция может быть использована для разрешения конфликтов по принципу «значение исходника». Такой тип разрешения конфликтов гарантирует, что обновления не применяются на реплике с неправильного источника.
Функция использует значение столбца из «предыдущего» изображения источника.
NDB$MAX()
Если значение столбца «временная метка» для заданной строки, полученной с источника, выше, чем на реплике, оно применяется; в противном случае оно не применяется на реплике. Это иллюстрируется следующим псевдокодом:
if (source_new_column_value > replica_current_column_value)
apply_update();
Эта функция может быть использована для разрешения конфликтов по принципу «наибольшая временная метка». Такой тип разрешения конфликтов гарантирует, что в случае конфликта версия строки, которая была обновлена последней, и будет сохранена.
Функция использует значение столбца из «последующего» изображения источника.
NDB$MAX_DELETE_WIN()
Это вариация на NDB$MAX(). Поскольку временная метка недоступна для операции удаления, удаление с использованием NDB$MAX() фактически обрабатывается как NDB$OLD, но в некоторых случаях это не оптимально. Для NDB$MAX_DELETE_WIN(), если значение столбца «временная метка» для заданной строки добавления или обновления существующей строки, полученной с источника, выше, чем на реплике, оно применяется. Однако операции удаления обрабатываются как всегда имеющие наибольшее значение. Это иллюстрируется следующим псевдокодом:
if ( (source_new_column_value > replica_current_column_value)
||
operation.type == "delete")
apply_update();
Эта функция может быть использована для разрешения конфликтов по принципу «наибольшая временная метка, удаление имеет приоритет». Такой тип разрешения конфликтов гарантирует, что в случае конфликта версия строки, которая была удалена или (иначе) обновлена последней, будет сохранена.
Как и в случае с NDB$MAX(), функция использует значение столбца из «последующего» изображения источника.
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(), а также требует использования событий строк двоичного журнала версии 2 (log_bin_use_v1_row_events равно 0), что добавляет накладные расходы в 2 байта на событие в двоичном журнале. Кроме того, все идентификаторы транзакций должны быть записаны в двоичный журнал вторичного кластера, используя --ndb-log-transaction-id, установленное на ON. Это добавляет переменное количество накладных расходов (до 13 байт на строку).
См. NDB$EPOCH().
NDB$EPOCH2()
Функция NDB$EPOCH2() аналогична функции NDB$EPOCH(), за исключением того, что NDB$EPOCH2() предоставляет обработку удаления-удаления с двунаправленной топологией репликации. В этом случае первичные и вторичные роли для двух источников назначаются путем установки системной переменной ndb_slave_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() следует помнить, что вторичный кластер применяет удаление с первичного, удаляя новую строку, пока она не будет восстановлена рефлексированной операцией. Теоретически, последующая вставка или обновление на вторичном кластере конфликтует с удалением с первичного, но в данном случае мы выбираем игнорировать это и разрешить вторичному кластеру «выиграть», в интересах предотвращения расхождения между кластерами. Другими словами, после удаления первичный кластер не обнаруживает конфликтов и вместо этого сразу же принимает последующие изменения вторичного кластера. Из-за этого состояние вторичного кластера может возвращаться к нескольким предыдущим сохраненным состояниям по мере его продвижения к конечному (стабильному) состоянию, и некоторые из них могут быть видимыми.
Также следует знать, что отражение всех операций со вторичного кластера обратно на первичный увеличивает размер журнала первичного кластера, а также требует большего объема полосы пропускания, использования ЦП и ввода-вывода на диск.
Применение отраженных операций на вторичном кластере зависит от состояния целевой строки на вторичном кластере. Применение или не применение отраженных изменений на вторичном кластере можно отследить, проверив переменные состояния Ndb_conflict_reflected_op_prepare_count и Ndb_conflict_reflected_op_discard_count. Количество примененных изменений просто равно разнице между этими двумя значениями (обратите внимание, что 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$OLDDELETE_ROW. может использоваться для ссылки на новые значения в операциях вставки и обновления — другими словами, операциях, использующих события colname$NEWWRITE_ROW, события UPDATE_ROW или оба типа событий. Если конфликтная операция не предоставляет значение для данного столбца ссылки, который не является первичным ключом, строка таблицы исключений содержит либо NULL, либо определённое значение по умолчанию для этого столбца.
Таблица mysql.ndb_replication читается, когда таблица данных настраивается для репликации, поэтому строка, соответствующая таблице, подлежащей репликации, должна быть вставлена в mysql.ndb_replication перед созданием таблицы, подлежащей репликации.
Переменные состояния обнаружения конфликтов
Несколько переменных состояния могут быть использованы для мониторинга обнаружения конфликтов. Вы можете увидеть, сколько строк было обнаружено в конфликте по NDB$EPOCH() с момента последней перезагрузки этой реплики из текущего значения системной переменной состояния Ndb_conflict_fn_epoch.
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(), было применено.
Количество раз, когда строка не была применена в результате конфликта “та же временная отметка выигрывает” на данном узле mysqld с момента последнего его перезапуска, задаётся глобальной переменной состояния Ndb_conflict_fn_old. Кроме инкрементирования Ndb_conflict_fn_old, первичный ключ строки, которая не была использована, вставляется в таблицу исключений, как объясняется в другом месте этого раздела.
См. также Раздел 21.4.3.9.3, «Переменные состояния NDB Cluster».
Примеры
В следующих примерах предполагается, что у вас уже настроено работающее реплицирование NDB кластера, как описано в разделе 21.7.5, «Подготовка NDB кластера к репликации», и разделе 21.7.6, «Запуск репликации NDB кластера (одиночный канал репликации)».
Пример NDB$MAX(). Предположим, что вы хотите включить механизм разрешения конфликтов «самая большая метка времени выигрывает» для таблицы test.t1, используя столбец mycol в качестве метки времени. Это можно сделать, выполнив следующие шаги:
Убедитесь, что вы запустили исходный mysqld с параметром
--ndb-log-update-as-write=OFF.-
На источнике выполните следующую
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); используется значение по умолчанию сервера. -
Создайте таблицу
test.t1:CREATE TABLE test.t1 (columnsmycol INT UNSIGNED,columns) ENGINE=NDB;Теперь при выполнении обновлений в этой таблице будет применяться разрешение конфликтов, и версия строки с наибольшим значением для
mycolбудет записана в реплику.
Другие параметры binlog_type, такие как NBT_UPDATED_ONLY_USE_UPDATE (6), должны использоваться для управления журналированием на источнике с помощью таблицы ndb_replication, а не с помощью параметров командной строки.
Пример NDB$OLD(). Предположим, что таблица NDB, подобная определенной здесь, реплицируется, и вы хотите включить механизм разрешения конфликтов «то же самое значение метки времени выигрывает» для обновлений этой таблицы:
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;
Для этого требуются следующие шаги в указанном порядке:
-
Сначала—и до создания
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. -
Создайте соответствующую таблицу исключений для
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в определении таблицы. Создайте таблицу
test.t2, как показано ранее.
Эти шаги должны быть выполнены для каждой таблицы, для которой вы хотите выполнить разрешение конфликтов с использованием NDB$OLD(). Для каждой такой таблицы должна быть соответствующая строка в mysql.ndb_replication, и должна быть таблица исключений в той же базе данных, что и реплицируемая таблица.
Обнаружение и разрешение конфликтов при чтении. NDB Cluster также поддерживает отслеживание операций чтения, что позволяет в конфигурациях циклической репликации управлять конфликтами между чтением заданной строки в одном кластере и обновлением или удалением этой же строки в другом. В этом примере используются таблицы 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 Cluster.
© 2025 Oracle
Licensed under the GPLv2 License.