### 23.7.11 NDB 集群复制冲突解决

-   [要求](mysql-cluster-replication-conflict-resolution.html#conflict-resolution-requirements)

-   [源列控制](mysql-cluster-replication-conflict-resolution.html#conflict-resolution-source-column)

-   [冲突解决控制](mysql-cluster-replication-conflict-resolution.html#conflict-resolution-control)

-   [冲突解决功能](mysql-cluster-replication-conflict-resolution.html#conflict-resolution-functions)

-   [冲突解决例外表](mysql-cluster-replication-conflict-resolution.html#conflict-resolution-exceptions-table)

-   [冲突检测状态变量](mysql-cluster-replication-conflict-resolution.html#conflict-detection-statvars)

-   [例子](mysql-cluster-replication-conflict-resolution.html#conflict-detection-examples)

[](<>)[](<>)[](<>)[](<>)[](<>)

当使用涉及多个源的复制设置（包括循环复制）时，不同的源可能会尝试使用不同的数据更新副本上的同一行。NDB Cluster Replication 中的冲突解决提供了一种解决此类冲突的方法，方法是允许使用用户定义的解决列来确定是否应在副​​本上应用对给定源的更新。

NDB Cluster 支持的某些类型的冲突解决（`新开发银行$OLD()`,`新开发银行$MAX()`,`NDB$MAX_DELETE_WIN()`) 将此用户定义的列实现为“时间戳”列（尽管它的类型不能[`时间戳`](datetime.html)，如本节后面所述）。这些类型的冲突解决总是逐行应用，而不是基于事务。基于时期的冲突解决功能`新开发银行$EPOCH()`和`NDB$EPOCH_TRANS()`比较 epoch 的复制顺序（因此这些函数是事务性的）。当发生冲突时，可以使用不同的方法来比较副本上的解析列值，如本节后面所述；可以在每个表的基础上设置使用的方法。

您还应该记住，应用程序有责任确保使用相关值正确填充解析列，以便解析函数在确定是否应用更新时可以做出适当的选择。

#### 要求

必须在源和副本上都做好冲突解决的准备工作。以下列表中描述了这些任务：

-   在写入二进制日志的源上，您必须确定发送哪些列（所有列或仅那些已更新的列）。这是通过应用 MySQL 服务器作为一个整体来完成的[**mysqld**](mysqld.html)启动选项[`--ndb-log-updated-only`](mysql-cluster-options-variables.html#option_mysqld_ndb-log-updated-only)（在本节后面描述）或在每个表的基础上通过条目`mysql.ndb_replication`表（见[数据库\_复制表](mysql-cluster-replication-schema.html#ndb-replication-ndb-replication)）。

    笔记

    如果您要复制具有非常大列的表（例如[`文本`](blob.html)要么[`斑点`](blob.html)列），[`--ndb-log-updated-only`](mysql-cluster-options-variables.html#option_mysqld_ndb-log-updated-only)也可用于减少二进制日志的大小并避免由于超出限制而可能导致的复制失败[`max_allowed_pa​​cket`](server-system-variables.html#sysvar_max_allowed_packet).

    看[第 17.5.1.20 节，“复制和最大\_允许\_包”](replication-features-max-allowed-packet.html), 有关此问题的更多信息。

-   在副本上，您必须确定应用哪种类型的冲突解决方案（“最新时间戳获胜”、“相同时间戳获胜”、“主要获胜”、“主要获胜、完成事务”或无）。这是使用`mysql.ndb_replication`系统表，基于每个表（请参阅[数据库\_复制表](mysql-cluster-replication-schema.html#ndb-replication-ndb-replication)）。

-   NDB Cluster 还支持读取冲突检测，即检测一个集群中给定行的读取与另一个集群中同一行的更新或删除之间的冲突。这需要通过设置获得独占读锁[`ndb_log_exclusive_reads`](mysql-cluster-options-variables.html#sysvar_ndb_log_exclusive_reads)在副本上等于 1。由冲突读取读取的所有行都记录在异常表中。有关详细信息，请参阅[读取冲突检测和解决](mysql-cluster-replication-conflict-resolution.html#conflict-resolution-read-conflicts).

    使用函数时`新开发银行$OLD()`,`新开发银行$MAX()`， 和`NDB$MAX_DELETE_WIN()`对于基于时间戳的冲突解决方案，我们通常将用于确定更新的列称为“时间戳”列。但是，该列的数据类型从不[`时间戳`](datetime.html);相反，它的数据类型应该是[`INT`](integer-types.html)([`整数`](integer-types.html)） 要么[`大整数`](integer-types.html).“时间戳”列也应该是`未签名`和`非空`.

    这`新开发银行$EPOCH()`和`NDB$EPOCH_TRANS()`本节后面讨论的函数通过比较应用于主 NDB Cluster 和辅助 NDB Cluster 的复制时期的相对顺序来工作，并且不使用时间戳。

#### 源列控制

[](<>)我们可以根据“之前”和“之后”图像来查看更新操作——即应用更新之前和之后表的状态。通常，当使用主键更新表时，“之前”图像不是很感兴趣；但是，当我们需要在每次更新的基础上确定是否在副本上使用更新的值时，我们需要确保将两个图像都写入源的二进制日志。这是通过[`--ndb-log-update-as-write`](mysql-cluster-options-variables.html#option_mysqld_ndb-log-update-as-write)选项[**mysqld**](mysqld.html)，如本节后面所述。

重要的

是记录完整的行还是只记录更新的列，是在 MySQL 服务器启动时决定的，不能在线更改；您必须重新启动[**mysqld**](mysqld.html)，或者开始一个新的[**mysqld**](mysqld.html)具有不同日志记录选项的实例。

[](<>)**记录全部或部分行。**  [](<>)为了解决冲突，有两种记录行的基本方法，由[`--ndb-log-updated-only`](mysql-cluster-options-variables.html#option_mysqld_ndb-log-updated-only)选项[**mysqld**](mysqld.html)：

-   记录完整的行（选项设置为`在`)

-   仅记录已更新的列数据——即其值已设置的列数据，无论此值是否实际更改。这是默认行为（选项设置为`离开`）。

    通常只记录更新的列就足够了，而且效率更高；但是，如果您需要记录完整的行，您可以通过设置[`--ndb-log-updated-only`](mysql-cluster-options-variables.html#option_mysqld_ndb-log-updated-only)到`0`要么`离开`.

[](<>)**将更改的数据记录为更新。**MySQL服务器的设置[`--ndb-log-update-as-write`](mysql-cluster-options-variables.html#option_mysqld_ndb-log-update-as-write)选项确定是否使用或不使用“之前”图像执行日志记录。因为冲突解决是在 MySQL 服务器的更新处理程序中完成的，所以有必要控制复制源执行的日志记录，以便更新是更新而不是写入；也就是说，即使这些替换现有行，更新也被视为现有行的更改而不是写入新行。该选项默认开启；换句话说，更新被视为写入。也就是说，更新默认写为`写行`二进制日志中的事件，而不是作为`更新行`事件。

要禁用该选项，请启动源[**mysqld**](mysqld.html)和`--ndb-log-update-as-write=0`要么`--ndb-log-update-as-write=OFF`.从 NDB 表复制到使用不同存储引擎的表时，您必须这样做；看[从 NDB 复制到其他存储引擎](mysql-cluster-replication-issues.html#mysql-cluster-replication-ndb-to-non-ndb)， 和[从 NDB 复制到非事务性存储引擎](mysql-cluster-replication-issues.html#mysql-cluster-replication-ndb-to-nontransactional)， 了解更多信息。

#### 冲突解决控制

[](<>)通常在可能发生冲突的服务器上启用冲突解决。与记录方法选择一样，它由`mysql.ndb_replication`桌子。

`NBT_UPDATED_ONLY_MINIMAL`和`NBT_UPDATED_FULL_MINIMAL`可以与`新开发银行$EPOCH()`,`新开发银行$EPOCH2()`， 和`NDB$EPOCH_TRANS()`，因为这些不需要不是主键的列的“之前”值。需要旧值的冲突解决算法，例如`新开发银行$MAX()`和`新开发银行$OLD()`, 不能正确使用这些`binlog_type`价值观。

#### 冲突解决功能

接下来的几段提供了有关可用于通过 NDB 复制进行冲突检测和解决的功能的详细信息。

[](<>)**NDB$OLD(列\_姓名）。**  [](<>)如果值*`列名`*在源和副本上都相同，则应用更新；否则，更新不会应用于副本，并将异常写入日志。下面的伪代码说明了这一点：

```
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();
```

[](<>)此功能可用于“最大时间戳获胜”冲突解决。这种类型的冲突解决可确保在发生冲突时，最近更新的行的版本是持续存在的版本。

重要的

此函数使用源的“后”图像中的列值。

[](<>)**新开发银行$MAX\_删除\_赢（）。**  [](<>)这是关于`新开发银行$MAX()`.由于没有时间戳可用于删除操作，删除使用`新开发银行$MAX()`实际上被处理为`新开发银行$OLD`，但对于某些用例，这不是最佳选择。为了`NDB$MAX_DELETE_WIN()`，如果给定行添加或更新来自源的现有行的“时间戳”列值高于副本上的值，则应用它。但是，删除操作被视为始终具有较高的值。下面的伪代码说明了这一点：

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

[](<>)此功能可用于“最大时间戳，删除获胜”的冲突解决。这种类型的冲突解决可确保在发生冲突时，已删除或（否则）最近更新的行的版本是持续存在的版本。

笔记

与`新开发银行$MAX()`，来自源的“后”图像的列值是此函数使用的值。

[](<>)**新开发银行$时代（）。**  [](<>) [](<>)这`新开发银行$EPOCH()`函数跟踪复制的 epoch 相对于源自副本的更改应用于副本集群的顺序。此相对排序用于确定源自副本的更改是否与源自本地的任何更改同时发生，因此可能存在冲突。

大部分内容在以下描述中`新开发银行$EPOCH()`也适用于`NDB$EPOCH_TRANS()`.任何例外都会在正文中注明。

`新开发银行$EPOCH()`是不对称的，在双向复制配置中的一个 NDB Cluster 上运行（有时称为“主动-主动”复制）。我们在这里指的是集群，它作为主集群运行，另一个集群作为辅助集群。主节点上的副本负责检测和处理冲突，而辅助节点上的副本不参与任何冲突检测或处理。

当主节点上的副本检测到冲突时，它会将事件注入到自己的二进制日志中以弥补这些冲突；这可确保辅助 NDB Cluster 最终与主 NDB Cluster 重新对齐，从而防止主 NDB Cluster 和辅助 NDB Cluster 发生分歧。这种补偿和重新调整机制要求主 NDB Cluster 始终赢得与辅助 NDB Cluster 的任何冲突——也就是说，在发生冲突时，始终使用主 NDB Cluster 的更改而不是辅助 NDB Cluster 的更改。这个“主要总是获胜”的规则具有以下含义：

-   更改数据的操作一旦在主服务器上提交，就会完全持久，并且不会因冲突检测和解决而撤消或回滚。

-   从主节点读取的数据是完全一致的。在主节点（本地或从副本）上提交的任何更改都不会在以后恢复。

-   如果主节点确定它们存在冲突，则更改辅助节点上的数据的操作稍后可能会被恢复。

-   在辅助节点上读取的各个行始终是自洽的，每一行始终反映辅助节点提交的状态或主节点提交的状态。

-   在辅助节点上读取的行集可能不一定在给定的单个时间点保持一致。为了`NDB$EPOCH_TRANS()`，这是一个瞬态；为了`新开发银行$EPOCH()`，它可以是一个持久状态。

-   假设一段足够长的时间没有任何冲突，辅助 NDB Cluster 上的所有数据（最终）都与主 NDB Cluster 的数据保持一致。

`新开发银行$EPOCH()`和`NDB$EPOCH_TRANS()`不需要任何用户模式修改或应用程序更改来提供冲突检测。但是，必须仔细考虑使用的模式和使用的访问模式，以验证整个系统的行为是否在指定的范围内。

每个`新开发银行$EPOCH()`和`NDB$EPOCH_TRANS()`函数可以带一个可选参数；这是用于表示 epoch 的低 32 位的位数，应设置为不小于此处所示计算的值：

```
CEIL( LOG2( TimeBetweenGlobalCheckpoints / TimeBetweenEpochs ), 1)
```

对于这些配置参数的默认值（分别为 2000 和 100 毫秒），这给出了 5 位的值，因此默认值 (6) 应该足够了，除非使用其他值[`全球检查点之间的时间`](mysql-cluster-ndbd-definition.html#ndbparam-ndbd-timebetweenglobalcheckpoints),[`时代之间的时间`](mysql-cluster-ndbd-definition.html#ndbparam-ndbd-timebetweenepochs)， 或两者。值太小可能会导致误报，而太大可能会导致数据库中过多的空间浪费。

两个都`新开发银行$EPOCH()`和`NDB$EPOCH_TRANS()`将冲突行的条目插入相关的异常表中，前提是这些表是根据本节其他地方描述的相同异常表模式规则定义的（请参阅[NDB$OLD(列\_姓名）](mysql-cluster-replication-conflict-resolution.html#mysql-cluster-replication-ndb-old)）。在创建要使用的数据表之前，您必须创建任何异常表。

与本节讨论的其他冲突检测功能一样，`新开发银行$EPOCH()`和`NDB$EPOCH_TRANS()`通过在`mysql.ndb_replication`表（见[数据库\_复制表](mysql-cluster-replication-schema.html#ndb-replication-ndb-replication)）。此场景中主 NDB Clusters 和辅助 NDB Clusters 的角色完全由`mysql.ndb_replication`表条目。

因为冲突检测算法采用`新开发银行$EPOCH()`和`NDB$EPOCH_TRANS()`是不对称的，你必须使用不同的值`server_id`主副本和辅助副本的条目。

之间的冲突`删除`单独的操作不足以触发冲突使用`新开发银行$EPOCH()`要么`NDB$EPOCH_TRANS()`，并且时期内的相对位置无关紧要。（错误 #18459944）

有关详细信息，请参阅[NDB$EPOCH() 的限制](mysql-cluster-replication-conflict-resolution.html#mysql-cluster-replication-ndb-epoch-limitations).

[](<>)**新开发银行$时代\_转（）。**  [](<>) `NDB$EPOCH_TRANS()`扩展`新开发银行$EPOCH()`功能。使用“primary wins all”规则以相同的方式检测和处理冲突（请参阅[新开发银行$EPOCH()](mysql-cluster-replication-conflict-resolution.html#mysql-cluster-replication-ndb-epoch)) 但附加条件是在发生冲突的同一事务中更新的任何其他行也被视为冲突。换句话说，在哪里`新开发银行$EPOCH()`重新对齐辅助节点上的各个冲突行，`NDB$EPOCH_TRANS()`重新调整有冲突的事务。

此外，任何可检测到依赖于冲突事务的事务也被视为冲突，这些依赖关系由辅助集群的二进制日志的内容确定。由于二进制日志仅包含数据修改操作（插入、更新和删除），因此仅使用重叠的数据修改来确定事务之间的依赖关系。

`NDB$EPOCH_TRANS()`受相同的条件和限制`新开发银行$EPOCH()`，此外还要求将所有事务 ID 记录在辅助节点的二进制日志中（[`--ndb-log-transaction-id`](mysql-cluster-options-variables.html#option_mysqld_ndb-log-transaction-id)选项），这会增加可变开销（每行最多 13 个字节）。已弃用的[`log_bin_use_v1_row_events`](replication-options-binary-log.html#sysvar_log_bin_use_v1_row_events)系统变量，默认为`离开`, 不得设置为`在`和`NDB$EPOCH_TRANS()`.

看[新开发银行$EPOCH()](mysql-cluster-replication-conflict-resolution.html#mysql-cluster-replication-ndb-epoch).

[](<>)**新开发银行$EPOCH2()。**  [](<>)这`新开发银行$EPOCH2()`功能类似于`新开发银行$EPOCH()`， 除了那个`新开发银行$EPOCH2()`使用双向复制拓扑提供删除-删除处理。在这种情况下，主要和次要角色通过设置[`ndb_slave_conflict_role`](mysql-cluster-options-variables.html#sysvar_ndb_slave_conflict_role)系统变量到每个源上的适当值（通常每个一个`基本的`,`中学`）。完成此操作后，辅助节点所做的修改会由主节点反射回辅助节点，然后辅助节点有条件地应用它们。

[](<>)**新开发银行$EPOCH2\_转（）。**  [](<>) `NDB$EPOCH2_TRANS()`扩展`新开发银行$EPOCH2()`功能。以相同的方式检测和处理冲突，并将主要和次要角色分配给复制集群，但附加条件是在发生冲突的同一事务中更新的任何其他行也被视为冲突。那是，`新开发银行$EPOCH2()`重新对齐辅助上的各个冲突行，而`NDB$EPOCH_TRANS()`重新调整有冲突的事务。

在哪里`新开发银行$EPOCH()`和`NDB$EPOCH_TRANS()`使用每行指定的元数据，每个最后修改的时期，在主节点上确定来自辅助节点的传入复制行更改是否与本地提交的更改并发；并发更改被认为是冲突的，随后的异常表更新和辅助节点的重新对齐。当在主数据库上删除一行时会出现问题，因此不再有任何最后修改的时期可用于确定是否有任何复制操作冲突，这意味着没有检测到冲突的删除操作。这可能会导致分歧，例如一个集群上的删除与另一个集群上的删除和插入同时发生；这就是为什么删除操作在使用时只能路由到一个集群`新开发银行$EPOCH()`和`NDB$EPOCH_TRANS()`.

`新开发银行$EPOCH2()`通过忽略任何删除-删除冲突，并避免任何潜在的结果分歧，绕过了刚刚描述的问题——在 PRIMARY 上存储有关已删除行的信息。这是通过将成功应用到辅助节点并从辅助节点复制回辅助节点的任何操作来实现的。在它返回到辅助节点时，它可用于在辅助节点上重新应用一个操作，该操作已被源自主节点的操作删除。

使用时`新开发银行$EPOCH2()`，您应该记住，辅助应用从主应用删除，删除新行，直到它被反射操作恢复。理论上，从属节点的后续插入或更新与从主节点的删除冲突，但在这种情况下，我们选择忽略这一点，让从节点“获胜”，以防止集群之间的分歧。换句话说，删除后，主节点不会检测到冲突，而是立即采用从节点的后续更改。因此，辅助节点的状态可以在进入最终（稳定）状态时重新访问多个先前提交的状态，其中一些可能是可见的。

您还应该知道，将所有操作从辅助服务器反射回主服务器会增加主服务器的 logbinary 日志的大小，以及对带宽、CPU 使用率和磁盘 I/O 的需求。

在辅助节点上应用反射操作取决于辅助节点上目标行的状态。可以通过检查[`Ndb_conflict_reflected_op_prepare_count`](mysql-cluster-options-variables.html#statvar_Ndb_conflict_reflected_op_prepare_count)和[`Ndb_conflict_reflected_op_discard_count`](mysql-cluster-options-variables.html#statvar_Ndb_conflict_reflected_op_discard_count)状态变量。应用的更改数量只是这两个值之间的差异（请注意`Ndb_conflict_reflected_op_prepare_count`总是大于或等于`Ndb_conflict_reflected_op_discard_count`）。

当且仅当以下两个条件都为真时，才会应用事件：

-   行的存在——即是否存在——与事件的类型一致。对于删除和更新操作，该行必须已经存在。对于插入操作，该行必须*不是*存在。

-   该行最后一次由主节点修改。修改可能是通过执行反射操作完成的。

    如果这两个条件都不满足，则反射操作将被辅助丢弃。

[](<>)**NDB$EPOCH() 的限制。**  [](<>)使用时当前适用以下限制`新开发银行$EPOCH()`执行冲突检测：

-   使用 NDB Cluster 纪元边界检测冲突，粒度与[`时代之间的时间`](mysql-cluster-ndbd-definition.html#ndbparam-ndbd-timebetweenepochs)（默认值：100 毫秒）。最小冲突窗口是对两个集群上相同数据的并发更新总是报告冲突的最短时间。这始终是一个非零的时间长度，并且大致与`2 *（延迟 + 排队 + TimeBetweenEpochs）`.这意味着——假设默认为[`时代之间的时间`](mysql-cluster-ndbd-definition.html#ndbparam-ndbd-timebetweenepochs)并且忽略集群之间的任何延迟（以及任何排队延迟）——最小冲突窗口大小约为 200 毫秒。在查看预期的应用程序“竞争”模式时，应考虑此最小窗口。

-   使用`新开发银行$EPOCH()`和`NDB$EPOCH_TRANS()`职能;每行需要 1 到 32 位的额外空间，具体取决于传递给函数的值。

-   删除操作之间的冲突可能会导致主从之间的分歧。当同时删除两个集群上的行时，可以检测到冲突，但不会记录，因为该行已被删除。这意味着在任何后续重新对齐操作的传播过程中都不会检测到进一步的冲突，这可能会导致分歧。

    删除应在外部序列化，或仅路由到一个集群。或者，应使用此类删除和任何后续插入以事务方式更新单独的行，以便可以跨行删除跟踪冲突。这可能需要更改应用程序。

-   使用时，当前仅支持双向“主动-主动”配置中的两个 NDB 集群`新开发银行$EPOCH()`要么`NDB$EPOCH_TRANS()`用于冲突检测。

-   表有[`斑点`](blob.html)要么[`文本`](blob.html)当前不支持列`新开发银行$EPOCH()`要么`NDB$EPOCH_TRANS()`.

#### 冲突解决例外表

[](<>) [](<>)要使用`新开发银行$OLD()`冲突解决功能，还需要创建一个异常表对应每个[`新开发银行`](mysql-cluster.html)使用这种类型的冲突解决的表。使用时也是如此`新开发银行$EPOCH()`要么`NDB$EPOCH_TRANS()`.此表的名称是要应用冲突解决的表的名称，字符串为`$EX`附加。（例如，如果原始表的名称是`我的表`，对应的异常表名应该是`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`， 和`数数`列，并且您对与原始表的主键中的列匹配的列使用与原始表中相同的名称。

如果异常表使用一个或多个可选列`NDB$OP_TYPE`,`NDB$CFT_CAUSE`， 要么`NDB$ORIG_TRANSID`在本节后面讨论，那么每个必需的列也必须使用前缀命名`新开发银行$`.如果需要，您可以使用`新开发银行$`即使您没有定义任何可选列，也可以使用前缀来命名所需列，但在这种情况下，必须使用前缀命名所有四个所需列。

在这些列之后，构成原始表主键的列应按照它们用于定义原始表的主键的顺序进行复制。与原表的主键列重复的列的数据类型应与原列的数据类型相同（或大于）。可以使用主键列的子集。

例外表必须使用[`新开发银行`](mysql-cluster.html)存储引擎。（一个例子使用`新开发银行$OLD()`本节稍后会显示一个例外表。）

可以选择在复制的主键列之后定义附加列，但不能在其中任何一个之前定义；任何此类额外的列都不能`非空`.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`列（如果使用）包含原始交易的 ID。该列应定义如下：

```
NDB$ORIG_TRANSID BIGINT UNSIGNED NOT NULL
```

`NDB$ORIG_TRANSID`是一个 64 位的值，由`新开发银行`.此值可用于关联来自相同或不同异常表的属于同一冲突事务的多个异常表条目。

可以命名不属于原始表主键的附加引用列`*`名称`*$旧`要么`*`名称`*$新`.`*`名称`*$旧`在更新和删除操作中引用旧值——也就是说，操作包含`DELETE_ROW`事件。`*`名称`*$新`可用于在插入和更新操作中引用新值——换句话说，使用`WRITE_ROW`事件，`UPDATE_ROW`事件，或两种类型的事件。如果冲突操作未为给定的非主键引用列提供值，则异常表行包含`空值`，或为该列定义的默认值。

重要的

这`mysql.ndb_replication`table 是在设置数据表进行复制时读取的，因此必须将要复制的表对应的行插入到`mysql.ndb_replication` *前*创建要复制的表。

#### 冲突检测状态变量

[](<>) [](<>)可以使用几个状态变量来监控冲突检测。您可以通过以下方式查看有多少行存在冲突`新开发银行$EPOCH()`因为这个副本最后一次从当前值重新启动[`Ndb_conflict_fn_epoch`](mysql-cluster-options-variables.html#statvar_Ndb_conflict_fn_epoch)系统状态变量。

[`Ndb_conflict_fn_epoch_trans`](mysql-cluster-options-variables.html#statvar_Ndb_conflict_fn_epoch_trans)提供直接发现冲突的行数`NDB$EPOCH_TRANS()`.[`Ndb_conflict_fn_epoch2`](mysql-cluster-options-variables.html#statvar_Ndb_conflict_fn_epoch2)和[`Ndb_conflict_fn_epoch2_trans`](mysql-cluster-options-variables.html#statvar_Ndb_conflict_fn_epoch2_trans)显示在冲突中发现的行数`新开发银行$EPOCH2()`和`NDB$EPOCH2_TRANS()`， 分别。实际重新对齐的行数，包括由于与其他冲突行属于相同事务或依赖于相同事务而受影响的行数，由下式给出[`Ndb_conflict_trans_row_reject_count`](mysql-cluster-options-variables.html#statvar_Ndb_conflict_trans_row_reject_count).

另一个服务器状态变量[`Ndb_conflict_fn_max`](mysql-cluster-options-variables.html#statvar_Ndb_conflict_fn_max)提供由于“最大时间戳获胜”冲突解决而在当前 SQL 节点上未应用行的次数，因为自上次[**mysqld**](mysqld.html)开始了。

由于在给定的“相同时间戳获胜”冲突解决而未应用行的次数[**mysqld**](mysqld.html)自上次重新启动以来由全局状态变量给出[`Ndb_conflict_fn_old`](mysql-cluster-options-variables.html#statvar_Ndb_conflict_fn_old).除了递增[`Ndb_conflict_fn_old`](mysql-cluster-options-variables.html#statvar_Ndb_conflict_fn_old)，未使用的行的主键被插入到异常表中，如本节其他地方所述。

也可以看看[第 23.4.3.9.3 节，“NDB 集群状态变量”](mysql-cluster-options-variables.html#mysql-cluster-status-variables).

#### 例子

以下示例假设您已经有一个有效的 NDB Cluster 复制设置，如中所述[第 23.7.5 节，“为复制准备 NDB 集群”](mysql-cluster-replication-preparation.html)， 和[第 23.7.6 节，“启动 NDB 集群复制（单复制通道）”](mysql-cluster-replication-starting.html).

[](<>)**NDB$MAX() 示例。**假设您希望在表上启用“最大时间戳获胜”冲突解决`测试.t1`, 使用列`麦考尔`作为“时间戳”。这可以使用以下步骤来完成：

1.  确保您已启动源[**mysqld**](mysqld.html)和[`--ndb-log-update-as-write=OFF`](mysql-cluster-options-variables.html#option_mysqld_ndb-log-update-as-write).

2.  在源上，执行此操作[`插入`](insert.html)陈述：

    ```
    INSERT INTO mysql.ndb_replication
        VALUES ('test', 't1', 0, NULL, 'NDB$MAX(mycol)');
    ```

    插入一个 0 到[`server_id`](replication-options.html#sysvar_server_id)表示访问此表的所有 SQL 节点都应使用冲突解决。如果您想在特定的[**mysqld**](mysqld.html)仅，使用实际的服务器 ID。

    插入`空值`进入`binlog_type`列与插入 0 (`NBT_DEFAULT`);使用服务器默认值。

3.  创建`测试.t1`桌子：

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

    现在，当对此表执行更新时，将应用冲突解决，并且具有最大值的行的版本`麦考尔`被写入副本。

笔记

其他`binlog_type`选项——例如`NBT_UPDATED_ONLY_USE_UPDATE`应该用于控制源上的日志记录，使用`ndb_replication`表而不是使用命令行选项。

[](<>)**NDB$OLD() 示例。**假设一个[`新开发银行`](mysql-cluster.html)正在复制此处定义的表，并且您希望为更新此表启用“相同的时间戳获胜”冲突解决方案：

```
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.  首先——并且*事先的*去创造`测试.t2`— 您必须在[`mysql.ndb_replication`](mysql-cluster-replication-schema.html#ndb-replication-ndb-replication)表，如下所示：

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

    的可能值`binlog_type`列在本节前面显示。价值`'NDB$OLD(mycol)'`应插入`冲突_fn`柱子。

2.  为`测试.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;
    ```

    我们可以包含附加列，以获取有关给定冲突的类型、原因和原始事务 ID 的信息。我们也不需要为原始表中的所有主键列提供匹配列。这意味着您可以像这样创建异常表：

    ```
    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$OP_TYPE`,`NDB$CFT_CAUSE`， 要么`NDB$ORIG_TRANSID`在表定义中。

3.  创建表`测试.t2`如前所示。

    对于您希望使用以下方法执行冲突解决的每个表，必须遵循这些步骤`新开发银行$OLD()`.对于每个这样的表，必须有一个对应的行`mysql.ndb_replication`，并且在与被复制的表相同的数据库中必须有一个异常表。

[](<>)**读取冲突检测和解决。**  [](<>) [](<>)NDB Cluster 还支持跟踪读取操作，这使得在循环复制设置中可以管理一个集群中给定行的读取与另一个集群中同一行的更新或删除之间的冲突。这个例子使用`员工`和`部`表来模拟一个场景，在该场景中，员工在源集群（以下我们称之为集群）上从一个部门转移到另一个部门*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.html)声明：

```
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      |
...
+-----+-------------+---------+
```

我们假设我们已经在使用一个exceptions表，该表包含四个必需的列（这些列用于该表的主键）、操作类型和原因的可选列，以及原始表的主键列，这些列是使用下面所示的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.*，我们创建一个新部门，然后使用以下SQL语句将员工编号999移动到该部门：

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

同时，在集群上*B*，另一个事务读取`受雇者`，如图所示：

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

[](<>)

冲突解决机制通常不会检测到冲突事务，因为冲突发生在读操作之间(`选择`)以及更新操作。你可以通过执行[`设置`](set-variable.html) [`ndb_日志_独占_读取`](mysql-cluster-options-variables.html#sysvar_ndb_log_exclusive_reads) `= 1`在副本群集上。以这种方式获取独占读锁会导致在源上读取的任何行被标记为需要在副本集群上解决冲突。如果我们在记录这些事务之前以这种方式启用独占读取，那么集群上的读取*B*被跟踪并发送到集群*A.*决议；随后检测到员工行上的冲突，并在集群上执行事务*B*被中止。

冲突在异常表（在集群上）中注册*A.*)作为一个`读一读`操作（参见[冲突解决例外表](mysql-cluster-replication-conflict-resolution.html#conflict-resolution-exceptions-table)，用于描述操作类型），如下所示：

```
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;
```

在本例中，匹配`哪里`第二个事务中的条件`选择`被读取，因此在例外表中被标记，如下所示：

```
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集群的单个实例中执行排他行锁定的方式。
