25.7.12 Разрешение конфликтов при репликации NDB Cluster
При использовании конфигурации репликации с несколькими источниками (включая циклическую репликацию), возможно, что различные источники могут пытаться обновить одну и ту же строку на реплике с разными данными. Разрешение конфликтов в репликации NDB Cluster предоставляет возможность разрешения таких конфликтов, позволяя использовать определяемый пользователем столбец разрешения для определения того, следует ли применять обновление из определенного источника на реплике.
Некоторые типы разрешения конфликтов, поддерживаемые 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.20, «Репликация и 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.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()
Если значение 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()” следующим образом:
Если конфликтующей записи нет, примените эту (это то же самое, что и
NDB$MAX()).-
В противном случае примените разрешение конфликта по принципу “выигрывает наибольшая метка времени”, следующим образом:
Если метка времени для входящей записи больше, чем у конфликтующей записи, примените входящую операцию.
Если метка времени для входящей записи не больше, отклоните входящую операцию записи.
При обработке операции вставки, 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(), как показано здесь:
Если конфликтующей записи нет, примените эту (это то же самое, что и
NDB$MAX_DELETE_WIN()).-
В противном случае примените разрешение конфликта по принципу “выигрывает наибольшая метка времени”, следующим образом:
Если метка времени для входящей записи больше, чем у конфликтующей записи, примените входящую операцию.
Если метка времени для входящей записи не больше, отклоните входящую операцию записи.
Обработка операций вставки, выполняемая 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 log, а также требует увеличения пропускной способности, использования ЦП и ввода-вывода на диск.
Применение отражённых операций на вторичном сервере зависит от состояния целевой строки на вторичном сервере. Можно отслеживать применение или неприменение отражённых изменений на вторичном сервере, проверяя переменные состояния 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(), было применено.
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, первичный ключ строки, которая не была использована, вставляется в таблицу исключений, как описано в другом месте данного раздела.
См. также Раздел 25.4.3.9.3, “Переменные состояния NDB кластера”.
Примеры
В следующих примерах предполагается, что у вас уже настроено реплицирование кластера NDB, как описано в разделе 25.7.5, «Подготовка кластера NDB к реплицированию», и разделе 25.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 также поддерживает отслеживание операций чтения, что позволяет управлять конфликтами между чтением определенной строки в одном кластере и обновлениями или удалениями этой же строки в другом кластере в установках циклической репликации. В этом примере используются таблицы 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 в операторе чтения второй транзакции, читаются и помечаются в таблице исключений, как показано здесь:
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.