• 首页
  • vue
  • TypeScript
  • JavaScript
  • scss
  • css3
  • html5
  • php
  • MySQL
  • redis
  • jQuery
  • NDB群集复制冲突解决

    当使用涉及多个主服务器的复制设置(包括循环复制)时,不同的主服务器可能会尝试使用不同的数据更新从服务器上的同一行。NDB群集复制中的冲突解决方案提供了一种解决此类冲突的方法,方法是允许使用用户定义的解决方案列来确定是否应在从属服务器上应用给定主服务器上的更新。

    通过NDB集群支持的某些类型的冲突解决的(NDB$OLD()NDB$MAX()NDB$MAX_DELETE_WIN())实现这种用户定义的列作为“时间戳”列(虽然它的类型不能为TIMESTAMP,因为在本节后面解释)。这些类型的冲突解决方案始终是逐行而不是事务性地应用。基于纪元的冲突解决功能NDB$EPOCH()NDB$EPOCH_TRANS()比较历元的复制顺序(因此这些功能是事务性的)。发生冲突时,可以使用不同的方法比较从属服务器上的分辨率列值,如本节后面所述。可以按表设置使用的方法。

    您还应牢记,应用程序负责确保使用相关值正确填充了“分辨率”列,以便在确定是否应用更新时,分辨率功能可以做出适当的选择。

    要求。必须在主机和从机上都准备好解决冲突的方法。下面的列表中描述了这些任务:

    • 在编写二进制日志的主数据库上,您必须确定发送哪些列(所有列或仅更新了那些列)。整个MySQL服务器都可以通过应用mysqld启动选项--ndb-log-updated-only(在本节后面进行介绍)来完成此操作,或者通过按表中的条目按mysql.ndb_replication表(参见ndb_replication系统表)在整个表上完成。

      注意

      如果要复制具有非常大的列(例如TEXTBLOB列)的表,--ndb-log-updated-only则对于减小主二进制日志和从二进制日志的大小以及避免由于超出而可能导致的复制失败也很有用max_allowed_packet

      有关此问题的更多信息,请参见“ Replication and max_allowed_packet”。

    • 在从属服务器上,您必须确定要应用哪种类型的冲突解决方案(“最新时间戳获胜”,“相同时间戳获胜”,“主要胜利”,“主要胜利,完成交易”,或无)。这是使用mysql.ndb_replication系统表按表完成的(请参阅 ndb_replication系统表)。
    • NDB群集还支持读取冲突检测,即检测一个群集中给定行的读取与另一群集中同一行的更新或删除之间的冲突。这需要通过ndb_log_exclusive_reads在从站上将其设置为1 获得的互斥读锁定。冲突读取读取的所有行均记录在例外表中。有关更多信息,请参阅阅读冲突检测和解决方案。

    使用功能NDB$OLD()NDB$MAX()以及NDB$MAX_DELETE_WIN()基于时间戳的冲突解决方案时,我们通常将用于确定更新的列称为“时间戳”列。但是,此列的数据类型从不为TIMESTAMP;相反,其数据类型应为INTINTEGER)或BIGINT。在“时间戳”栏也应UNSIGNEDNOT NULL

    本节后面讨论的NDB$EPOCH()NDB$EPOCH_TRANS()功能通过比较应用于主要和辅助NDB群集的复制时期的相对顺序来工作,并且不使用时间戳。

    主列控件。我们可以按照“之前”和“之后”图像来参见更新操作,也就是说,应用更新前后的表状态。通常,当使用主键更新表时,“ before ”图像不是很重要;但是,当需要根据每次更新确定是否在复制从属服务器上使用更新的值时,我们需要确保两个映像都写入主服务器的二进制日志中。这是通过mysqld--ndb-log-update-as-write选项完成的,如本节后面所述。

    重要

    启动MySQL服务器时决定是否记录完整的行还是仅记录更新的列,并且不能在线更改。您必须重新启动mysqld,或使用不同的日志记录选项启动一个新的mysqld实例。

    记录全部或部分行(--ndb-log-updated-only选项)

    属性
    命令行格式--ndb-log-updated-only[={OFF|ON}]
    系统变量ndb_log_updated_only
    范围Global
    动态
    SET_VAR提示适用没有
    类型布尔型
    默认值ON

    为了解决冲突,有两种记录行的基本方法,具体取决于mysqld--ndb-log-updated-only选项的设置:

    • 记录完整的行
    • 仅记录已更新的列数据,即已设置其值的列数据,而不管此值是否实际更改。这是默认行为。

    通常只记录更新的列就足够了,而且效率更高。但是,如果需要记录完整的行,可以通过设置--ndb-log-updated-only0或来进行记录OFF

    --ndb-log-update-as-write选项:将更改的数据记录为更新

    属性
    命令行格式--ndb-log-update-as-write[={OFF|ON}]
    系统变量ndb_log_update_as_write
    范围Global
    动态
    SET_VAR提示适用没有
    类型布尔型
    默认值ON

    MySQL Server --ndb-log-update-as-write选项的设置确定是否使用“ before ”映像执行日志记录。由于冲突解决是在MySQL Server的更新处理程序中完成的,因此有必要控制主服务器上的日志记录,以使更新是更新而非写入。也就是说,将更新视为现有行中的更改,而不是写入新行(即使这些替换了现有行)。默认情况下,此选项处于打开状态。换句话说,更新被视为写入。(也就是说,默认情况下,更新被write_row记录为二进制日志中的update_row事件,而不是事件。)

    要关闭该选项,请使用或启动主mysqld。从NDB表复制到使用其他存储引擎的表时,必须执行此操作。有关更多信息,请参阅从NDB复制到其他存储引擎,以及从NDB复制到非事务存储引擎。--ndb-log-update-as-write=0--ndb-log-update-as-write=OFF

    冲突解决控制。通常在可能发生冲突的服务器上启用冲突解决。与日志记录方法选择一样,它由表中的条目启用mysql.ndb_replication

    ndb_replication系统表。要启用冲突解决方案,有必要根据冲突解决方案类型和使用的方法在主数据库,从数据库或两者上ndb_replicationmysql系统数据库中创建一个表。该表用于按表控制日志记录和冲突解决功能,并且每个表在复制中涉及一行。ndb_replication将在要解决冲突的服务器上创建并填充控制信息。在简单的主从设置中,也可以在从属设备上本地更改数据,这通常是从属设备。在更复杂的主-主(2-way)复制模式中,通常是所有涉及的主。每行mysql.ndb_replication对应于要复制的表,并指定如何记录和解决该表的冲突(即要使用的冲突解决功能,如果有的话)。该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);
    

    该表中的列将在接下来的几段中进行介绍。

    D b。包含要复制的表的数据库的名称。您可以使用一个或两个通配符_,也可以使用它们%作为数据库名称的一部分。匹配类似于为LIKE操作员实施的匹配。

    table_name。要复制的表的名称。表名可以包含通配符_和之一或全部%。匹配类似于为LIKE操作员实施的匹配。

    server_id。该表所在的MySQL实例(SQL节点)的唯一服务器ID。

    binlog_type。要使用的二进制日志记录的类型。如下表所示确定:

    binlog_type值以及内部值和描述

    内部价值描述
    0NBT_DEFAULT使用服务器默认值
    1个NBT_NO_LOGGING不要将此表记录在二进制日志中
    2NBT_UPDATED_ONLY仅记录更新的属性
    3NBT_FULL记录完整行,即使未更新(MySQL服务器默认行为)
    4NBT_USE_UPDATE(仅用于生成NBT_UPDATED_ONLY_USE_UPDATENBT_FULL_USE_UPDATE值-不能单独使用)
    5[未使用]---
    6NBT_UPDATED_ONLY_USE_UPDATE(等于NBT_UPDATED_ONLY | NBT_USE_UPDATE使用更新的属性,即使值不变
    7NBT_FULL_USE_UPDATE(等于NBT_FULL | NBT_USE_UPDATE即使值不变,也要使用整行

    冲突_fn。要应用的冲突解决功能。必须将此功能指定为以下列表中显示的功能之一:

    • NDB $ OLD(column_name)
    • NDB $ MAX(column_name)
    • NDB $ MAX_DELETE_WIN()
    • NDB $ EPOCH()和NDB $ EPOCH_TRANS()
    • NDB $ EPOCH_TRANS()
    • NDB $ EPOCH2()
    • NDB $ EPOCH2_TRANS()
    • NULL:指示冲突解决方案将不用于相应的表。

    在接下来的几段中将介绍这些功能。

    NDB $ OLD(column_name)。如果column_name主机和从机上的值相同,那么将应用更新;否则,将应用更新。否则,更新将不会应用于从属服务器,并且会将异常写入日志。以下伪代码对此进行了说明:

    if (master_old_column_value == slave_current_column_value)
      apply_update();
    else
      log_exception();
    

    此功能可用于“相同价值取胜”冲突解决。这种类型的冲突解决方案确保不会将错误的主服务器上的更新应用到从服务器上。

    重要

    此功能使用了母版的“ before ”图像中的列值。

    NDB $ MAX(column_name)。如果来自主机的给定行的“ timestamp ”列值高于从机上的行,则将应用该列;否则,它不会应用于从站。以下伪代码对此进行了说明:

    if (master_new_column_value > slave_current_column_value)
      apply_update();
    

    此功能可用于“最大的时间戳获胜”冲突解决。这种类型的冲突解决方案确保在发生冲突的情况下,最近更新的行的版本是持久的版本。

    重要

    此功能使用来自母版的“ after ”图像的列值。

    NDB $ MAX_DELETE_WIN()。这是的变体NDB$MAX()。由于没有时间戳可用于删除操作,NDB$MAX()实际上使用进行删除使用NDB$OLD。但是,对于某些用例,这不是最佳的。对于NDB$MAX_DELETE_WIN(),如果添加或更新来自主服务器的现有行的给定行的“时间戳”列值高于从服务器上的行,则将应用该值。但是,删除操作始终被视为具有较高的值。以下伪代码对此进行了说明:

    if ( (master_new_column_value > slave_current_column_value)
            ||
          operation.type == "delete")
      apply_update();
    

    该功能可用于“最大时间戳,删除胜利”冲突解决。这种类型的冲突解决方案确保在发生冲突的情况下,已删除或(否则)最近更新的行的版本是持久的版本。

    注意

    与一样NDB$MAX(),来自母版“ after ”图像的列值就是该功能使用的值。

    NDB $ EPOCH()和NDB $ EPOCH_TRANS()。NDB$EPOCH()功能跟踪相对于从属服务器上的更改,在从属服务器NDB群集上应用复制纪元的顺序。此相对排序用于确定源自于从属服务器的更改是否与本地发起的任何更改同时发生,因此有可能发生冲突。

    后面的描述中的大多数内容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)应是足够的,除非其它值被用于TimeBetweenGlobalCheckpointsTimeBetweenEpochs或两者。太小的值可能会导致误报,而太大的值可能会导致数据库浪费过多的空间。

    双方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(),并且历元内的相对位置无关紧要。(缺陷号18459944)

    冲突检测状态变量。几个状态变量可用于监视冲突检测。您可以看到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群集状态变量”。

    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()
    • 为表BLOBTEXT列目前没有与任何支持NDB$EPOCH()NDB$EPOCH_TRANS()

    NDB $ EPOCH_TRANS()。NDB$EPOCH_TRANS()扩展NDB$EPOCH()功能。使用“主要赢得所有人”规则以相同的方式检测和处理冲突(请参阅 NDB $ EPOCH()和NDB $ EPOCH_TRANS()),但要特别注意的是,发生冲突的同一事务中的任何其他行都将更新也被视为有冲突。换句话说,在NDB$EPOCH()次级NDB$EPOCH_TRANS()节点上重新对齐各个冲突行的地方,在冲突事务上重新对齐。

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

    NDB$EPOCH_TRANS()受到与相同的条件和限制NDB$EPOCH(),并且还要求所有事务ID都记录在辅助数据库的二进制日志(该--ndb-log-transaction-id选件)中,这会增加可变开销(每行最多13个字节)。不推荐使用的log_bin_use_v1_row_events系统变量,默认为OFF,不能设置到ONNDB$EPOCH_TRANS()

    请参阅NDB $ EPOCH()和NDB $ EPOCH_TRANS()。

    状态信息。服务器状态变量Ndb_conflict_fn_max提供自上次启动mysqld以来由于“最大时间戳获胜”冲突解决而导致行未应用于当前SQL节点的次数。

    自从上次重新启动行以来,由于给定的mysqld“相同的时间戳获胜”冲突解决而没有应用行的次数由全局状态变量给出。除了递增之外,未使用的行的主键也会插入到异常表中,如本节稍后所述。Ndb_conflict_fn_oldNdb_conflict_fn_old

    NDB $ EPOCH2()。NDB$EPOCH2()功能类似于NDB$EPOCH(),除了NDB$EPOCH2()提供使用循环复制(“ master-master ”)拓扑的删除/删除处理。在这种情况下,通过将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()通过忽略任何删除-删除冲突并避免任何潜在的结果分歧,从而绕过上述问题(在PRIMARY上存储有关已删除行的信息)。这是通过反映成功应用到辅助节点并从辅助节点复制回辅助节点的任何操作来完成的。当它返回到辅助服务器时,可以用来在辅助服务器上重新应用操作,该操作已被来自主服务器的操作删除。

    使用时NDB$EPOCH2(),应记住辅助节点从主节点应用删除,删除新行,直到通过反射操作将其恢复。从理论上讲,对辅助服务器的后续插入或更新与从主服务器上的删除冲突,但是在这种情况下,我们选择忽略此操作,并允许辅助服务器“获胜”,以防止群集之间的差异。换句话说,删除后,主数据库不会检测到冲突,而是立即采用辅助数据库的以下更改。因此,当辅助状态进入最终(稳定)状态时,辅助状态可以重新访问多个先前提交的状态,并且其中某些状态可能是可见的。

    您还应该意识到,将所有从辅助数据库回到主数据库的操作反映出来,会增加主数据库日志二进制日志的大小,以及对带宽,CPU使用率和磁盘I / O的要求。

    在辅助服务器上应用反射操作取决于辅助服务器上目标行的状态。可以通过检查Ndb_conflict_reflected_op_prepare_countNdb_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$]master_server_id INT UNSIGNED,
        [NDB$]master_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$]master_server_id, [NDB$]master_epoch, [NDB$]count)
    ) ENGINE=NDB;
    

    前四列是必需的。前四列的名称以及与原始表的主键列匹配的列的名称并不重要;然而,我们建议为了清楚和一致的原因,您使用这里显示的名字server_idmaster_server_idmaster_epoch,和count列,并使用相同的名称作为原始表匹配那些在原表的主键列。

    如果例外表使用一个或多个可选列NDB$OP_TYPENDB$CFT_CAUSENDB$ORIG_TRANSID在本节后面讨论,则还必须使用prefix命名每个必需列NDB$。如果需要,NDB$即使未定义任何可选列,也可以使用前缀来命名所需的列,但是在这种情况下,必须使用前缀来命名所有四个必需列。

    在这些列之后,应按用来定义原始表主键的顺序复制构成原始表主键的列。复制原始表的主键列的列的数据类型应与原始列的数据类型相同(或大于原始列的数据类型)。可以使用主键列的子集。

    无论使用哪种NDB群集版本,例外表都必须使用NDB存储引擎。(NDB$OLD()本节后面显示了一个使用例外表的示例。)

    可以选择在复制的主键列之后定义其他列,但不能在其中任何一个之前定义;任何此类额外的列都不能为NOT NULL。NDB Cluster支持三个额外的,预定义的可选列NDB$OP_TYPENDB$CFT_CAUSENDB$ORIG_TRANSID,这在接下来的几个段落中。

    NDB$OP_TYPE:此列可用于获取导致冲突的操作类型。如果使用此列,请按如下所示对其进行定义:

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

    WRITE_ROWUPDATE_ROWDELETE_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_ROWWRITE_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位值NDB。此值可用于关联来自相同或不同异常表的属于同一冲突事务的多个异常表条目。

    不属于原始表主键的其他参考列可以命名为colname$OLDcolname$NEWcolname$OLD在更新和删除操作(即包含DELETE_ROW事件的操作)中引用旧值。colname$NEW可用于在插入和更新操作中引用新值,换句话说,是使用WRITE_ROW事件,UPDATE_ROW事件或同时使用两种类型的事件的操作。如果冲突操作没有为给定的非主键引用列提供值,则例外表行包含NULL或该列的定义默认值。

    重要

    mysql.ndb_replication设置要复制的数据表时,将读取该表,因此在创建要复制的表mysql.ndb_replication之前,必须将与要复制的表相对应的行插入其中。

    例子

    以下示例假定您已经具有有效的NDB群集复制设置,如“准备要复制的NDB群集”和“启动NDB群集复制(单个复制通道)”中所述。

    NDB $ MAX()示例。假设您希望使用列作为“时间戳记”在表上启用“最大的时间戳记赢”冲突解决方案。可以使用以下步骤完成此操作:test.t1mycol

    1. 请确保您已经启动了主的mysqld--ndb-log-update-as-write=OFF
    2. 在主服务器上,执行以下INSERT语句:

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

      在中插入0 server_id表示访问该表的所有SQL节点均应使用冲突解决方案。如果只想在特定的mysqld上使用冲突解决,请使用实际的服务器ID。

      插入NULL到所述binlog_type列具有作为插入0(相同的效果NBT_DEFAULT);使用服务器默认值。

    3. 创建test.t1表:

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

      现在,在此表上完成更新后,将应用冲突解决方案,并将具有最大价值的行版本mycol写入从站。

    注意

    其他binlog_type选项(例如,NBT_UPDATED_ONLY_USE_UPDATE应使用该选项来控制通过ndb_replication表而不是命令行选项来登录主数据库)。

    NDB $ OLD()示例。假设NDB正在复制一个表(如此处定义的表),并且您希望为该表的更新启用“ same timestamp wins ”冲突解决:

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

    需要按照显示的顺序执行以下步骤:

    1. 首先,创建之前test.t2必须在mysql.ndb_replication表中插入一行,如下所示:

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

      binlog_type列的可能值在本节的前面显示。该值'NDB$OLD(mycol)'应插入到conflict_fn列中。

    2. 为创建一个适当的例外表test.t2。此处显示的表创建语句包括所有必填列。必须在这些列之后以及在定义表的主键之前声明任何其他列。

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

      我们可以包括其他列,以提供有关给定冲突的类型,原因和原始事务处理ID的信息。我们也不需要为原始表中的所有主键列提供匹配的列。这意味着您可以像下面那样创建例外表:

      CREATE TABLE test.t2$EX  (
          NDB$server_id INT UNSIGNED,
          NDB$master_server_id INT UNSIGNED,
          NDB$master_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$master_server_id, NDB$master_epoch, NDB$count)
      )   ENGINE=NDB;
      
      注意

      NDB$前缀是必需的四个必需列,因为我们包括列的至少一个NDB$OP_TYPENDB$CFT_CAUSENDB$ORIG_TRANSID在表定义。

    3. test.t2如上所示创建表。

    对于要使用进行冲突解决的每个表,必须遵循这些步骤NDB$OLD()。对于每个这样的表,必须在中有对应的行mysql.ndb_replication,并且与要复制的表在同一数据库中必须有一个例外表。

    读取冲突检测和解决方案。 NDB Cluster还支持跟踪读取操作,这使得在循环复制设置中可以管理一个群集中给定行的读取与另一群集中同一行的更新或删除之间的冲突。本示例使用employeedepartment表来建模一个场景,在该场景中,员工在主群集(以下称为群集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$master_server_id INT UNSIGNED,
        NDB$master_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$master_server_id, NDB$master_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上,另一个事务从读取employee,如下所示:

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

    冲突解决机制通常不会检测到冲突的事务,因为冲突发生在读取(SELECT)和更新操作之间。您可以通过在从属群集上执行来避免此问题。以这种方式获取排他读取锁定会导致将在主服务器上读取的任何行标记为需要在从属群集上解决冲突。如果我们在记录这些事务之前以这种方式启用了互斥读取,则会跟踪对群集B的读取并将其发送到群集A进行解析;员工行上的冲突将被检测到,并且集群B上的事务将中止。SETndb_log_exclusive_reads= 1

    冲突在操作的异常表(在群集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群集的单个实例中执行排他行锁定的方式。