MySQL8 中文参考(九十九)
25.7.5 准备 NDB 集群进行复制
原文:
dev.mysql.com/doc/refman/8.0/en/mysql-cluster-replication-preparation.html
准备 NDB 集群进行复制包括以下步骤:
-
检查所有 MySQL 服务器的版本兼容性(参见第 25.7.2 节,“NDB 集群复制的一般要求”)。
-
在源集群上创建一个具有适当权限的复制帐户,使用以下两个 SQL 语句:
mysql*S*> CREATE USER '*replica_user*'@'*replica_host*' -> IDENTIFIED BY '*replica_password*'; mysql*S*> GRANT REPLICATION SLAVE ON *.* -> TO '*replica_user*'@'*replica_host*';在上一条语句中,*
replica_user是复制帐户用户名,replica_host是复制的主机名或 IP 地址,replica_password*是要分配给此帐户的密码。例如,要创建一个名为
myreplica的复制用户帐户,从名为replica-host的主机登录,并使用密码53cr37,请使用以下CREATE USER和GRANT语句:mysql*S*> CREATE USER 'myreplica'@'replica-host' -> IDENTIFIED BY '53cr37'; mysql*S*> GRANT REPLICATION SLAVE ON *.* -> TO 'myreplica'@'replica-host';出于安全原因,最好使用一个唯一的用户帐户—不用于任何其他目的—用于复制帐户。
-
设置复制使用源。使用mysql客户端,可以通过
CHANGE REPLICATION SOURCE TO语句(从 NDB 8.0.23 开始)或CHANGE MASTER TO语句(在 NDB 8.0.23 之前)来实现:mysql*R*> CHANGE MASTER TO -> MASTER_HOST='*source_host*', -> MASTER_PORT=*source_port*, -> MASTER_USER='*replica_user*', -> MASTER_PASSWORD='*replica_password*';从 NDB 8.0.23 开始,您还可以使用以下语句:
mysql*R*> CHANGE REPLICATION SOURCE TO -> SOURCE_HOST='*source_host*', -> SOURCE_PORT=*source_port*, -> SOURCE_USER='*replica_user*', -> SOURCE_PASSWORD='*replica_password*';在上一条语句中,*
source_host是复制源的主机名或 IP 地址,source_port是复制连接到源时复制使用的端口,replica_user是在源上为复制设置的用户名,replica_password*是在上一步中为该用户帐户设置的密码。例如,要告诉复制使用在上一步中创建的具有复制帐户的 MySQL 服务器,其主机名为
rep-source,请使用以下语句:mysql*R*> CHANGE MASTER TO -> MASTER_HOST='rep-source', -> MASTER_PORT=3306, -> MASTER_USER='myreplica', -> MASTER_PASSWORD='53cr37';从 NDB 8.0.23 开始,您还可以使用以下语句:
mysql*R*> CHANGE REPLICATION SOURCE TO -> SOURCE_HOST='rep-source', -> SOURCE_PORT=3306, -> SOURCE_USER='myreplica', -> SOURCE_PASSWORD='53cr37';要查看可与此语句一起使用的选项的完整列表,请参见第 15.4.2.1 节,“CHANGE MASTER TO 语句”。
为了提供复制备份功能,还需要在开始复制过程之前向复制的
my.cnf文件中添加一个--ndb-connectstring选项。有关详细信息,请参见第 25.7.9 节,“NDB 集群复制的 NDB 集群备份”。有关副本可以在
my.cnf中设置的其他选项,请参见第 19.1.6 节,“复制和二进制日志选项和变量”。 -
如果源集群已在使用中,则可以创建源的备份并将其加载到副本中,以减少副本与源同步所需的时间。如果副本也在运行 NDB 集群,则可以使用第 25.7.9 节,“NDB 集群复制的 NDB 集群备份”中描述的备份和恢复过程来实现这一点。
ndb-connectstring=*management_host*[:*port*]如果在副本上不使用 NDB 集群,则可以使用以下命令在源上创建备份:
shell*S*> mysqldump --master-data=1然后通过将转储文件复制到副本上导入生成的数据转储。之后,您可以使用mysql客户端将数据从转储文件导入到副本数据库中,如下所示,其中*
dump_file是使用mysqldump在源上生成的文件的名称,db_name*是要复制的数据库的名称:shell*R*> mysql -u root -p *db_name* < *dump_file*要查看与mysqldump一起使用的完整选项列表,请参见第 6.5.4 节,“mysqldump — A Database Backup Program”。
注意
如果您以这种方式将数据复制到副本中,请确保在所有数据加载完成之前停止副本尝试连接到源以开始复制。您可以通过在命令行上使用
--skip-slave-start选项启动副本,通过在副本的my.cnf文件中包含skip-slave-start,或者从 NDB 8.0.24 开始,通过设置skip_slave_start系统变量来实现这一点。从 NDB 8.0.26 开始,使用--skip-replica-start或skip_replica_start。一旦数据加载完成,请按照下面两节中概述的额外步骤进行操作。 -
确保每个充当复制源的 MySQL 服务器都分配了唯一的服务器 ID,并启用了二进制日志记录,使用基于行的格式。(参见 Section 19.2.1, “Replication Formats”.)此外,我们强烈建议启用
replica_allow_batching系统变量(NDB 8.0.26 及更高版本;在 NDB 8.0.26 之前,请使用slave_allow_batching)。从 NDB 8.0.30 开始,默认情况下已启用此功能。如果您使用的是 NDB Cluster 8.0.30 之前的版本,您还应考虑增加与
--ndb-batch-size和--ndb-blob-write-batch-bytes选项一起使用的值。在 NDB 8.0.30 及更高版本中,请使用--ndb-replica-batch-size来设置副本上用于写入的批量大小,而不是--ndb-batch-size,以及使用--ndb-replica-blob-write-batch-bytes而不是--ndb-blob-write-batch-bytes来确定复制应用程序用于写入 blob 数据的批量大小。所有这些选项都可以在源服务器的my.cnf文件中设置,或者在启动源 mysqld 进程时通过命令行设置。有关更多信息,请参见 Section 25.7.6, “Starting NDB Cluster Replication (Single Replication Channel)”")。
25.7.6 启动 NDB 集群复制(单个复制通道)
原文:
dev.mysql.com/doc/refman/8.0/en/mysql-cluster-replication-starting.html
本节概述了使用单个复制通道启动 NDB 集群复制的过程。
-
通过发出以下命令启动 MySQL 复制源服务器,其中*
id*是该服务器的唯一 ID(参见第 25.7.2 节,“NDB 集群复制的一般要求”):shell*S*> mysqld --ndbcluster --server-id=*id* \ --log-bin --ndb-log-bin &这将使用正确的日志格式启动服务器的mysqld进程,并启用二进制日志记录。在 NDB 8.0 中,还需要显式启用对
NDB表更新的日志记录,使用--ndb-log-bin选项;这是与 NDB 集群以前版本的一个变化,以前版本中此选项默认启用。注意
你也可以使用
--binlog-format=MIXED来启动源,这样在集群之间复制时会自动使用基于行的复制。不支持基于语句的二进制日志记录用于 NDB 集群复制(参见第 25.7.2 节,“NDB 集群复制的一般要求”)。 -
按照以下方式启动 MySQL 复制服务器:
shell*R*> mysqld --ndbcluster --server-id=*id* &在刚刚显示的命令中,*
id*是复制服务器的唯一 ID。在复制上不需要启用日志记录。注意
除非你希望立即开始复制,延迟复制线程的启动直到适当的
START REPLICA语句已经发出,如下面的第 4 步所述。你可以通过在命令行上使用--skip-slave-start选项,通过在复制的my.cnf文件中包含skip-slave-start,或在 NDB 8.0.24 及更高版本中,通过设置skip_slave_start系统变量来实现这一点。在 NDB 8.0.26 及更高版本中,使用--skip-replica-start和skip_replica_start。 -
必须将复制服务器与源服务器的复制二进制日志同步。如果源上之前没有运行二进制日志记录,请在复制服务器上运行以下语句:
mysql*R*> CHANGE MASTER TO -> MASTER_LOG_FILE='', -> MASTER_LOG_POS=4;从 NDB 8.0.23 开始,你还可以使用以下语句:
mysql*R*> CHANGE REPLICATION SOURCE TO -> SOURCE_LOG_FILE='', -> SOURCE_LOG_POS=4;这指示副本从日志的起始点开始读取源服务器的二进制日志。否则,即如果您正在使用备份从源加载数据,请参阅 Section 25.7.8,“NDB Cluster 复制实现故障切换”,了解在这种情况下如何获取
SOURCE_LOG_FILE|MASTER_LOG_FILE和SOURCE_LOG_POS|MASTER_LOG_POS的正确值。 -
最后,通过在副本上的mysql客户端发出以下命令,指示副本开始应用复制:
mysql*R*> START SLAVE;在 NDB 8.0.22 及更高版本中,您还可以使用以下语句:
mysql*R*> START REPLICA;这也启动了从源到副本的数据和更改的传输。
也可以使用两个复制通道,类似于下一节描述的过程;这种方法与使用单个复制通道的区别在 Section 25.7.7,“NDB Cluster 复制使用两个复制通道”中有所涵盖。
也可以通过启用批量更新来提高集群复制性能。可以通过在副本的mysqld进程上设置系统变量replica_allow_batching(NDB 8.0.26 及更高版本)或slave_allow_batching(NDB 8.0.26 之前)来实现。通常,更新在接收到时立即应用。但是,使用批处理会导致每个批次应用 32 KB 的更新;这可能会导致更高的吞吐量和更少的 CPU 使用,特别是在单个更新相对较小的情况下。
注意
批处理是基于每个时代的基础进行的;属于多个事务的更新可以作为同一批发送。
当达到时代结束时,所有未完成的更新都会被应用,即使更新总量不到 32 KB。
批处理可以在运行时打开和关闭。要在运行时激活它,您可以使用以下两个语句之一:
SET GLOBAL slave_allow_batching = 1;
SET GLOBAL slave_allow_batching = ON;
从 NDB 8.0.26 开始,您可以(并且应该)改用以下语句之一:
SET GLOBAL replica_allow_batching = 1;
SET GLOBAL replica_allow_batching = ON;
如果特定批次导致问题(例如,效果似乎没有正确复制的语句),可以使用以下两个语句之一来停用批处理:
SET GLOBAL slave_allow_batching = 0;
SET GLOBAL slave_allow_batching = OFF;
从 NDB 8.0.26 开始,您可以(并且应该)改用以下语句之一:
SET GLOBAL replica_allow_batching = 0;
SET GLOBAL replica_allow_batching = OFF;
您可以通过适当的SHOW VARIABLES语句来检查当前是否正在使用批处理,例如:
mysql> SHOW VARIABLES LIKE 'slave%';
在 NDB 8.0.26 及更高版本中,请使用以下语句:
mysql> SHOW VARIABLES LIKE 'replica%';
25.7.7 使用两个复制通道进行 NDB 集群复制
原文:
dev.mysql.com/doc/refman/8.0/en/mysql-cluster-replication-two-channels.html
在一个更完整的示例场景中,我们设想使用两个复制通道来提供冗余,从而防范单个复制通道可能的故障。这需要总共四个复制服务器,两个源服务器在源集群上,两个复制品服务器在复制品集群上。在接下来的讨论中,我们假设分配了如下所示的唯一标识符:
表 25.73 文本中描述的 NDB 集群复制服务器
| 服务器 ID | 描述 |
|---|---|
| 1 | 源 - 主要复制通道(S) |
| 2 | 源 - 次要复制通道(S') |
| 3 | 复制品 - 主要复制通道(R) |
| 4 | 复制品 - 次要复制通道(R') |
使用两个通道设置复制与设置单个复制通道并没有根本不同。首先,必须启动主和次要复制源服务器的mysqld进程,然后启动主和次要复制品的进程。可以通过在每个复制品上发出START REPLICA语句来启动复制进程。下面显示了需要发出的命令和顺序:
-
启动主复制源:
shell*S*> mysqld --ndbcluster --server-id=1 \ --log-bin & -
启动次要复制源:
shell*S'*> mysqld --ndbcluster --server-id=2 \ --log-bin & -
启动主复制品服务器:
shell*R*> mysqld --ndbcluster --server-id=3 \ --skip-slave-start & -
启动次要复制品服务器:
shell*R'*> mysqld --ndbcluster --server-id=4 \ --skip-slave-start & -
最后,通过在主复制品上执行
START REPLICA语句来启动主通道上的复制。mysql*R*> START SLAVE;从 NDB 8.0.22 开始,您还可以使用以下语句:
mysql*R*> START REPLICA;警告
此时只需启动主通道。只有在主复制通道失败时才需要启动次要复制通道,如第 25.7.8 节“使用 NDB 集群复制实现故障切换”中所述。同时运行多个复制通道可能导致在复制品上创建不需要的重复记录。
如前所述,在复制品上不需要启用二进制日志记录。
25.7.8 使用 NDB 集群复制实现故障转移
译文:
dev.mysql.com/doc/refman/8.0/en/mysql-cluster-replication-failover.html
如果主要集群复制过程失败,可以切换到次要复制通道。以下过程描述了完成此操作所需的步骤。
-
获取最近全局检查点(GCP)的时间。也就是说,您需要确定副本集群上的
ndb_apply_status表中的最新时代,可以使用以下查询找到:mysql*R'*> SELECT @latest:=MAX(epoch) -> FROM mysql.ndb_apply_status;在循环复制拓扑中,每个主机上都运行源和副本时,当您使用
ndb_log_apply_status=1时,NDB 集群时代会写入副本的二进制日志中。这意味着ndb_apply_status表包含了此主机上的副本以及任何其他充当此主机上运行的复制源服务器的副本的主机的信息。在这种情况下,您需要确定此副本上的最新时代,排除在此副本的二进制日志中未列在
CHANGE REPLICATION SOURCE TO|CHANGE MASTER TO语句的IGNORE_SERVER_IDS选项中的任何其他副本的时代。排除这些时代的原因是,mysql.ndb_apply_status表中具有与IGNORE_SERVER_IDS列表中的匹配的服务器 ID 的行,从CHANGE REPLICATION SOURCE TO|CHANGE MASTER TO语句中的本副本的源准备的行也被视为来自本地服务器,除了具有副本自身服务器 ID 的行。您可以从SHOW REPLICA STATUS的输出中检索此列表作为Replicate_Ignore_Server_Ids。我们假设您已经获得了此列表,并将其替换为此处显示的查询中的*ignore_server_ids*,该查询与上一个查询的版本一样,将最大时代选择到名为@latest的变量中:mysql*R'*> SELECT @latest:=MAX(epoch) -> FROM mysql.ndb_apply_status -> WHERE server_id NOT IN (*ignore_server_ids*);在某些情况下,使用要包含的服务器 ID 列表和在前述查询的
WHERE条件中的server_id IN *server_id_list*可能更简单或更有效(或两者兼有)。 -
使用第 1 步中显示的查询获取的信息,从源集群的
ndb_binlog_index表中获取相应的记录。您可以使用以下查询从源集群的
ndb_binlog_index表中获取所需的记录:mysql*S'*> SELECT -> @file:=SUBSTRING_INDEX(next_file, '/', -1), -> @pos:=next_position -> FROM mysql.ndb_binlog_index -> WHERE epoch = @latest;这些是自主要复制通道故障以来在源上保存的记录。我们在这里使用了一个用户变量
@latest来表示第 1 步中获得的值。当然,一个 mysqld 实例无法直接访问在另一个服务器实例上设置的用户变量。这些值必须手动或通过应用程序“插入”到第二个查询中。重要
在执行
START REPLICA之前,您必须确保副本mysqld使用--slave-skip-errors=ddl_exist_errors启动。否则,复制可能因重复的 DDL 错误而停止。 -
现在可以通过在次要副本服务器上运行以下查询来同步次要通道:
mysql*R'*> CHANGE MASTER TO -> MASTER_LOG_FILE='@file', -> MASTER_LOG_POS=@pos;在 NDB 8.0.23 及更高版本中,您还可以使用此处显示的语句:
mysql*R'*> CHANGE REPLICATION SOURCE TO -> SOURCE_LOG_FILE='@file', -> SOURCE_LOG_POS=@pos;再次我们使用了用户变量(在这种情况下是
@file和@pos)来表示第 2 步中获得的值,并在第 3 步中应用;在实践中,这些值必须手动插入或使用能够访问涉及的两个服务器的应用程序。注意
@file是一个字符串值,例如'/var/log/mysql/replication-source-bin.00001',因此在 SQL 或应用程序代码中使用时必须加引号。然而,由@pos表示的值则不应加引号。尽管 MySQL 通常会尝试将字符串转换为数字,但这种情况是一个例外。 -
您现在可以通过在次要副本上发出适当的命令来启动次要通道的复制mysqld:
mysql*R'*> START SLAVE;在 NDB 8.0.22 或更高版本中,您还可以使用以下语句:
mysql*R'*> START REPLICA;
一旦次要复制通道激活,您可以调查主要通道的故障并进行修复。执行这些精确的操作取决于主要通道失败的原因。
警告
只有在主要复制通道失败时才应启动次要复制通道。同时运行多个复制通道可能导致副本上创建不需要的重复记录。
如果故障仅限于单个服务器,则理论上应该可以从 S 复制到 R',或者从 S' 复制到 R。
25.7.9 NDB 集群复制的 NDB 集群备份
原文:
dev.mysql.com/doc/refman/8.0/en/mysql-cluster-replication-backups.html
25.7.9.1 NDB 集群复制:自动同步副本到源二进制日志
25.7.9.2 使用 NDB 集群复制进行时点恢复
本节讨论使用 NDB 集群复制进行备份和从备份中恢复。我们假设复制服务器已经按照之前的说明进行了配置(请参阅第 25.7.5 节,“为 NDB 集群准备复制”,以及紧随其后的各节)。完成这些步骤后,进行备份然后从备份中恢复的过程如下:
-
有两种不同的方法可以启动备份。
-
**方法 A. ** 此方法要求在启动复制过程之前,在源服务器上先启用集群备份过程。可以通过在
my.cnf文件中的[mysql_cluster]部分中包含以下行来完成,其中management_host是源集群的NDB管理服务器的 IP 地址或主机名,port是管理服务器的端口号:ndb-connectstring=*management_host*[:*port*]注意
只有在未使用默认端口(1186)时才需要指定端口号。有关 NDB 集群中端口和端口分配的更多信息,请参见第 25.3.3 节,“NDB 集群的初始配置”。
在这种情况下,可以通过在复制源上执行此语句来启动备份:
shell*S*> ndb_mgm -e "START BACKUP" -
**方法 B. ** 如果
my.cnf文件未指定管理主机的位置,则可以通过将此信息作为START BACKUP命令的一部分传递给NDB管理客户端来启动备份过程。可以按照以下方式执行此操作,其中management_host和port是管理服务器的主机名和端口号:shell*S*> ndb_mgm *management_host*:*port* -e "START BACKUP"在我们之前概述的场景中(请参阅第 25.7.5 节,“为 NDB 集群准备复制”),将执行如下操作:
shell*S*> ndb_mgm rep-source:1186 -e "START BACKUP"
-
-
将集群备份文件复制到正在上线的复制品。运行源集群的每个为ndbd进程的系统上都有集群备份文件,所有这些文件都必须复制到复制品以确保成功恢复。备份文件可以复制到复制品的管理主机所在计算机上的任何目录中,只要 MySQL 和 NDB 二进制文件在该目录中具有读取权限。在这种情况下,我们假设这些文件已复制到目录
/var/BACKUPS/BACKUP-1。虽然复制集群不必具有与源相同数量的数据节点,但强烈建议此数量相同。当复制服务器启动时,必须防止复制过程启动。您可以通过在命令行上使用
--skip-slave-start选项启动复制品,通过在复制品的my.cnf文件中包含skip-slave-start,或在 NDB 8.0.24 或更高版本中,通过设置skip_slave_start系统变量来实现这一点。 -
在复制品集群上创建任何在源集群上存在且需要复制的数据库。
重要
必须在复制品集群中的每个 SQL 节点上执行与要复制的每个数据库对应的
CREATE DATABASE(或CREATE SCHEMA)语句。 -
在mysql客户端中使用以下语句重置复制集群:
mysql*R*> RESET SLAVE;在 NDB 8.0.22 或更高版本中,您还可以使用此语句:
mysql*R*> RESET REPLICA; -
现在,您可以使用ndb_restore命令依次为每个备份文件启动集群恢复过程。对于其中的第一个,有必要包含
-m选项以恢复集群元数据,如下所示:shell*R*> ndb_restore -c *replica_host*:*port* -n *node-id* \ -b *backup-id* -m -r *dir**
dir*是备份文件在复制品上放置的目录路径。对于剩余备份文件对应的ndb_restore命令,不应使用-m选项。从具有四个数据节点的源集群中恢复(如第 25.7 节“NDB 集群复制”中所示的图表),其中备份文件已复制到目录
/var/BACKUPS/BACKUP-1,在复制品上执行的正确命令序列可能如下所示:shell*R*> ndb_restore -c replica-host:1186 -n 2 -b 1 -m \ -r ./var/BACKUPS/BACKUP-1 shell*R*> ndb_restore -c replica-host:1186 -n 3 -b 1 \ -r ./var/BACKUPS/BACKUP-1 shell*R*> ndb_restore -c replica-host:1186 -n 4 -b 1 \ -r ./var/BACKUPS/BACKUP-1 shell*R*> ndb_restore -c replica-host:1186 -n 5 -b 1 -e \ -r ./var/BACKUPS/BACKUP-1重要
在这个例子中,ndb_restore 的最后调用中需要
-e(或--restore-epoch)选项,以确保时代写入副本的mysql.ndb_apply_status表。没有这些信息,副本无法与源正确同步。 (参见 Section 25.5.23, “ndb_restore — Restore an NDB Cluster Backup”.) -
现在,您需要从副本的
ndb_apply_status表中获取最新的时代(如 Section 25.7.8, “Implementing Failover with NDB Cluster Replication” 中所讨论的):mysql*R*> SELECT @latest:=MAX(epoch) FROM mysql.ndb_apply_status; -
使用前一步骤中获得的
@latest作为时代值,您可以从源的mysql.ndb_binlog_index表中获取正确的起始位置@pos和正确的二进制日志文件@file。此处显示的查询从逻辑恢复位置之前应用的最后时代的Position和File列中获取这些值:mysql*S*> SELECT -> @file:=SUBSTRING_INDEX(File, '/', -1), -> @pos:=Position -> FROM mysql.ndb_binlog_index -> WHERE epoch > @latest -> ORDER BY epoch ASC LIMIT 1;如果当前没有复制流量,您可以通过在源上运行
SHOW MASTER STATUS并使用输出中Position列中显示的值来获取类似信息,该值为所有文件中具有最大值后缀的文件的File列中显示的值。在这种情况下,您必须确定这是哪个文件,并在下一步手动提供名称或通过脚本解析输出。 -
使用前一步骤中获得的值,现在可以在副本的 mysql 客户端中发出适当的命令。在 NDB 8.0.23 及更高版本中,请使用以下
CHANGE REPLICATION SOURCE TO语句:mysql*R*> CHANGE REPLICATION SOURCE TO -> SOURCE_LOG_FILE='@file', -> SOURCE_LOG_POS=@pos;在 NDB 8.0.23 之前,您必须使用此处显示的
CHANGE MASTER TO语句:mysql*R*> CHANGE MASTER TO -> MASTER_LOG_FILE='@file', -> MASTER_LOG_POS=@pos; -
现在,副本知道从源的哪个二进制日志文件的哪个点开始读取数据,您可以使用此语句使副本开始复制:
mysql*R*> START SLAVE;从 NDB 8.0.22 开始,您还可以使用以下语句:
mysql*R*> START REPLICA;
要在第二个复制通道上执行备份和恢复,只需重复这些步骤,根据需要用次要源和副本的主机名和 ID 替换主要源和副本服务器的名称,并在它们上运行前面的语句。
有关执行集群备份和从备份中恢复集群的更多信息,请参阅第 25.6.8 节,“NDB 集群的在线备份”。
原文:
dev.mysql.com/doc/refman/8.0/en/mysql-cluster-replication-auto-sync.html
25.7.9.1 NDB 集群复制:自动同步副本到源二进制日志
可以自动化前一节描述的大部分过程(参见第 25.7.9 节,“NDB 集群备份与 NDB 集群复制”)。以下 Perl 脚本reset-replica.pl作为您可以执行此操作的示例。
#!/user/bin/perl -w
# file: reset-replica.pl
# Copyright (c) 2005, 2020, Oracle and/or its affiliates. All rights reserved.
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to:
# Free Software Foundation, Inc.
# 59 Temple Place, Suite 330
# Boston, MA 02111-1307 USA
#
# Version 1.1
######################## Includes ###############################
use DBI;
######################## Globals ################################
my $m_host='';
my $m_port='';
my $m_user='';
my $m_pass='';
my $s_host='';
my $s_port='';
my $s_user='';
my $s_pass='';
my $dbhM='';
my $dbhS='';
####################### Sub Prototypes ##########################
sub CollectCommandPromptInfo;
sub ConnectToDatabases;
sub DisconnectFromDatabases;
sub GetReplicaEpoch;
sub GetSourceInfo;
sub UpdateReplica;
######################## Program Main ###########################
CollectCommandPromptInfo;
ConnectToDatabases;
GetReplicaEpoch;
GetSourceInfo;
UpdateReplica;
DisconnectFromDatabases;
################## Collect Command Prompt Info ##################
sub CollectCommandPromptInfo
{
### Check that user has supplied correct number of command line args
die "Usage:\n
reset-replica >source MySQL host< >source MySQL port< \n
>source user< >source pass< >replica MySQL host< \n
>replica MySQL port< >replica user< >replica pass< \n
All 8 arguments must be passed. Use BLANK for NULL passwords\n"
unless @ARGV == 8;
$m_host = $ARGV[0];
$m_port = $ARGV[1];
$m_user = $ARGV[2];
$m_pass = $ARGV[3];
$s_host = $ARGV[4];
$s_port = $ARGV[5];
$s_user = $ARGV[6];
$s_pass = $ARGV[7];
if ($m_pass eq "BLANK") { $m_pass = '';}
if ($s_pass eq "BLANK") { $s_pass = '';}
}
############### Make connections to both databases #############
sub ConnectToDatabases
{
### Connect to both source and replica cluster databases
### Connect to source
$dbhM
= DBI->connect(
"dbi:mysql:database=mysql;host=$m_host;port=$m_port",
"$m_user", "$m_pass")
or die "Can't connect to source cluster MySQL process!
Error: $DBI::errstr\n";
### Connect to replica
$dbhS
= DBI->connect(
"dbi:mysql:database=mysql;host=$s_host",
"$s_user", "$s_pass")
or die "Can't connect to replica cluster MySQL process!
Error: $DBI::errstr\n";
}
################ Disconnect from both databases ################
sub DisconnectFromDatabases
{
### Disconnect from source
$dbhM->disconnect
or warn " Disconnection failed: $DBI::errstr\n";
### Disconnect from replica
$dbhS->disconnect
or warn " Disconnection failed: $DBI::errstr\n";
}
###################### Find the last good GCI ##################
sub GetReplicaEpoch
{
$sth = $dbhS->prepare("SELECT MAX(epoch)
FROM mysql.ndb_apply_status;")
or die "Error while preparing to select epoch from replica: ",
$dbhS->errstr;
$sth->execute
or die "Selecting epoch from replica error: ", $sth->errstr;
$sth->bind_col (1, \$epoch);
$sth->fetch;
print "\tReplica epoch = $epoch\n";
$sth->finish;
}
####### Find the position of the last GCI in the binary log ########
sub GetSourceInfo
{
$sth = $dbhM->prepare("SELECT
SUBSTRING_INDEX(File, '/', -1), Position
FROM mysql.ndb_binlog_index
WHERE epoch > $epoch
ORDER BY epoch ASC LIMIT 1;")
or die "Prepare to select from source error: ", $dbhM->errstr;
$sth->execute
or die "Selecting from source error: ", $sth->errstr;
$sth->bind_col (1, \$binlog);
$sth->bind_col (2, \$binpos);
$sth->fetch;
print "\tSource binary log file = $binlog\n";
print "\tSource binary log position = $binpos\n";
$sth->finish;
}
########## Set the replica to process from that location #########
sub UpdateReplica
{
$sth = $dbhS->prepare("CHANGE MASTER TO
MASTER_LOG_FILE='$binlog',
MASTER_LOG_POS=$binpos;")
or die "Prepare to CHANGE MASTER error: ", $dbhS->errstr;
$sth->execute
or die "CHANGE MASTER on replica error: ", $sth->errstr;
$sth->finish;
print "\tReplica has been updated. You may now start the replica.\n";
}
# end reset-replica.pl
原文:
dev.mysql.com/doc/refman/8.0/en/mysql-cluster-replication-pitr.html
25.7.9.2 使用 NDB 集群复制进行时点恢复
NDB 集群表的时点恢复,即恢复自某一特定时间点以来所做的数据更改,是在恢复将服务器恢复到备份时的状态的完整备份之后进行的。使用 NDB 集群和 NDB 集群复制进行 NDB 集群表的时点恢复可以通过使用本地NDB数据备份(通过在ndb_mgm客户端中发出CREATE BACKUP命令)和恢复ndb_binlog_index表(从使用mysqldump制作的转储中)来完成。
要执行 NDB 集群的时点恢复,需要按照以下步骤进行:
-
使用
START BACKUP命令在ndb_mgm客户端中备份集群中的所有NDB数据库(参见第 25.6.8 节,“NDB 集群的在线备份”)。 -
在恢复集群之前的某个时间点,备份
mysql.ndb_binlog_index表。最简单的方法可能是使用mysqldump来完成此任务。同时备份二进制日志文件。根据您的需求,这个备份应该定期更新,甚至可能每小时更新一次。
-
(发生灾难性故障或错误。)
-
定位最近的备份。
-
清除数据节点文件系统(使用ndbd
--initial或 ndbmtd")--initial)。注意
从 NDB 8.0.21 开始,磁盘数据表空间和日志文件将通过
--initial删除。以前,��要手动删除这些文件。 -
使用
DROP TABLE或TRUNCATE TABLE命令处理mysql.ndb_binlog_index表。 -
执行ndb_restore,恢复所有数据。在运行ndb_restore时,必须包括
--restore-epoch选项,以便正确填充ndb_apply_status表。(有关更多信息,请参见 Section 25.5.23, “ndb_restore — 恢复 NDB 集群备份”.) -
从mysqldump的输出中恢复
ndb_binlog_index表,并根据需要从备份中恢复二进制日志文件。 -
找到最近应用的时代,即
ndb_apply_status表中的最大epoch列值,作为用户变量@LATEST_EPOCH(强调):SELECT *@LATEST_EPOCH*:=MAX(epoch) FROM mysql.ndb_apply_status; -
找到与
ndb_binlog_index表中的@LATEST_EPOCH对应的最新二进制日志文件(@FIRST_FILE)和位置(Position列值):SELECT Position, *@FIRST_FILE*:=File FROM mysql.ndb_binlog_index WHERE epoch > *@LATEST_EPOCH* ORDER BY epoch ASC LIMIT 1; -
使用mysqlbinlog,重放给定文件和位置的二进制日志事件,直到故障点。(参见 Section 6.6.9, “mysqlbinlog — 用于处理二进制日志文件的实用程序”.)
另请参阅 Section 9.5, “时间点(增量)恢复”,了解有关二进制日志、复制和增量恢复的更多信息。
25.7.10 NDB 集群复制:双向和循环复制
原文:
dev.mysql.com/doc/refman/8.0/en/mysql-cluster-replication-multi-source.html
可以使用 NDB 集群在两个集群之间进行双向复制,也可以在任意数量的集群之间进行循环复制。
循环复制示例。 在接下来的几段中,我们考虑涉及三个 NDB 集群(编号为 1、2 和 3)的复制设置示例,其中集群 1 充当集群 2 的复制源,集群 2 充当集群 3 的源,集群 3 充当集群 1 的源。每个集群都有两个 SQL 节点,SQL 节点 A 和 B 属于集群 1,SQL 节点 C 和 D 属于集群 2,SQL 节点 E 和 F 属于集群 3。
只要满足以下条件,就支持使用这些集群进行循环复制:
-
所有源和副本上的 SQL 节点都是相同的。
-
所有作为源和副本的 SQL 节点都启用了系统变量
log_replica_updates(从 NDB 8.0.26 开始)或log_slave_updates(NDB 8.0.26 及更早版本)。
这种循环复制设置如下图所示:
图 25.15 NDB 集群循环复制,所有源均为副本
在这种情况下,集群 1 中的 SQL 节点 A 复制到集群 2 中的 SQL 节点 C;SQL 节点 C 复制到集群 3 中的 SQL 节点 E;SQL 节点 E 复制到 SQL 节点 A。换句话说,复制线(图表中的曲线箭头表示)直接连接所有用作复制源和副本的 SQL 节点。
也可以设置循环复制,使得并非所有源 SQL 节点也是副本,如下所示:
图 25.16 NDB 集群循环复制,不是所有源都是副本
在这种情况下,每个集群中的不同 SQL 节点被用作复制源和副本。您不应该启动任何带有系统变量log_replica_updates(NDB 8.0.26 及更高版本)或log_slave_updates(NDB 8.0.26 之前)的 SQL 节点。NDB 集群的这种循环复制方案,其中复制线路(在图中由曲线箭头表示)是不连续的,应该是可能的,但需要注意的是,这种方案尚未经过彻底测试,因此仍然被视为实验性。
使用 NDB 本地备份和恢复初始化副本集群。 在设置循环复制时,可以通过在一个 NDB 集群上使用管理客户端START BACKUP命令创建备份,然后在另一个 NDB 集群上使用ndb_restore应用此备份来初始化副本集群。这不会自动在充当副本的第二个 NDB 集群的 SQL 节点上创建二进制日志;为了导致创建二进制日志,您必须在该 SQL 节点上发出SHOW TABLES语句;在运行START REPLICA之前应该这样做。这是一个已知问题。
多源故障转移示例。 在本节中,我们讨论了具有服务器 ID 1、2 和 3 的三个 NDB 集群的多源 NDB 集群复制设置中的故障转移。在这种情况下,集群 1 复制到集群 2 和 3;集群 2 也复制到集群 3。这种关系如下所示:
图 25.17 具有 3 个源的 NDB 集群多主复制
换句话说,数据通过两种不同的路径从集群 1 复制到集群 3:直接和通过集群 2。
并非所有参与多源复制的 MySQL 服务器都必须同时充当源和副本,给定的 NDB 集群可能会为不同的复制通道使用不同的 SQL 节点。这种情况如下所示:
图 25.18 具有 MySQL 服务器的 NDB 集群多源复制
作为副本的 MySQL 服务器必须启用系统变量 log_replica_updates(从 NDB 8.0.26 开始)或 log_slave_updates(NDB 8.0.26 及更早版本)。在前面的图表中还显示了哪些 mysqld 进程需要此选项。
注意
使用 log_replica_updates 或 log_slave_updates 系统变量对未作为副本运行的服务器没有影响。
当复制集群之一宕机时,就会出现故障切换的需求。在本例中,我们考虑 Cluster 1 丢失服务的情况,因此 Cluster 3 丢失了来自 Cluster 1 的 2 个更新源。由于 NDB 集群之间的复制是异步的,不能保证 Cluster 3 直接源自 Cluster 1 的更新比通过 Cluster 2 接收的更新更近。您可以通过确保 Cluster 3 追赶 Cluster 2 关于来自 Cluster 1 的更新来处理这个问题。在 MySQL 服务器方面,这意味着您需要将 MySQL 服务器 C 的任何未完成更新复制到服务器 F。
在服务器 C 上执行以下查询:
mysqlC> SELECT @latest:=MAX(epoch)
-> FROM mysql.ndb_apply_status
-> WHERE server_id=1;
mysqlC> SELECT
-> @file:=SUBSTRING_INDEX(File, '/', -1),
-> @pos:=Position
-> FROM mysql.ndb_binlog_index
-> WHERE orig_epoch >= @latest
-> AND orig_server_id = 1
-> ORDER BY epoch ASC LIMIT 1;
注意
您可以通过向 ndb_binlog_index 表添加适当的索引来提高此查询的性能,从而显着加快故障切换时间。有关更多信息,请参见 第 25.7.4 节,“NDB Cluster Replication Schema and Tables”。
从服务器 C 手动复制 @file 和 @pos 的值到服务器 F(或让您的应用程序执行相应操作)。然后,在服务器 F 上执行以下 CHANGE REPLICATION SOURCE TO 语句(NDB 8.0.23 及更高版本)或 CHANGE MASTER TO 语句(NDB 8.0.23 之前):
mysqlF> CHANGE MASTER TO
-> MASTER_HOST = 'serverC'
-> MASTER_LOG_FILE='@file',
-> MASTER_LOG_POS=@pos;
从 NDB 8.0.23 开始,您还可以使用以下语句:
mysqlF> CHANGE REPLICATION SOURCE TO
-> SOURCE_HOST = 'serverC'
-> SOURCE_LOG_FILE='@file',
-> SOURCE_LOG_POS=@pos;
完成后,在 MySQL 服务器 F 上发出 START REPLICA 语句;这将导致来自服务器 B 的任何丢失更新被复制到服务器 F。
CHANGE REPLICATION SOURCE TO | CHANGE MASTER TO 语句还支持一个 IGNORE_SERVER_IDS 选项,该选项接受一个逗号分隔的服务器 ID 列表,并导致来自相应服务器的事件被忽略。有关更多信息,请参见 第 15.4.2.1 节,“CHANGE MASTER TO 语句”,以及 第 15.7.7.36 节,“SHOW SLAVE | REPLICA STATUS 语句”。有关此选项如何与 ndb_log_apply_status 变量交互的信息,请参见 第 25.7.8 节,“使用 NDB 集群复制实现故障切换”。
25.7.11 NDB 集群使用多线程应用程序的复制
原文:
dev.mysql.com/doc/refman/8.0/en/mysql-cluster-replication-mta.html
从 NDB 8.0.33 开始,NDB 复制支持使用通用的 MySQL 服务器多线程应用程序机制(MTA),允许在副本上并行应用独立的二进制日志事务,从而增加了峰值复制吞吐量。
需求
MySQL 服务器 MTA 实现将单独的二进制日志事务的处理委托给一组工作线程(其大小是可配置的),并协调工作线程以确保二进制日志中编码的事务依赖关系得到尊重,并在必要时保持提交顺序(参见第 19.2.3 节,“复制线程”)。要在 NDB 集群中使用此功能,必须满足以下三个条件:
-
二进制日志事务依赖关系在源端确定。
为了实现这一点,必须在源端将
binlog_transaction_dependency_tracking服务器系统变量设置为WRITESET。这在 NDB 8.0.33 及更高版本中受支持。(默认值为COMMIT_ORDER。)NDB中的写集维护工作由 MySQL 二进制日志注入线程执行,作为准备和提交每个时代事务到二进制日志的一部分。这需要额外的资源,并可能降低峰值吞吐量。 -
事务依赖关系被编码到二进制日志中。
NDB 8.0.33 及更高版本支持
--ndb-log-transaction-dependency启动选项用于mysqld; 将此选项设置为ON以启用将NDB事务依赖关系写入二进制日志。 -
副本配置为使用多个工作线程。
NDB 8.0.33 及更高版本支持将
replica_parallel_workers设置为非零值,以控制副本上的工作线程数。默认值为 4。
MTA 配置:源
NDB MTA 的源mysqld配置必须包括以下显式设置:
-
binlog_transaction_dependency_tracking必须设置为WRITESET。 -
复制源 mysqld 必须使用
--ndb-log-transaction-dependency=ON启动。
如果设置了,replica_parallel_type必须为LOGICAL_CLOCK(默认值)。
注意
NDB不支持replica_parallel_type=DATABASE。
此外,建议您将用于跟踪二进制日志事务写入集的内存量设置为源上的binlog_transaction_dependency_history_size为*E* * *P*,其中*E是平均时代大小(即每个时代的操作数),P*是最大预期并行性。有关更多信息,请参见写集跟踪内存使用情况。
MTA 配置:副本
NDB MTA 的副本mysqld配置要求replica_parallel_workers大于 1。首次启用 MTA 时的推荐起始值为 4,这是默认值。
此外,replica_preserve_commit_order必须为ON。这也是默认值。
事务依赖性和写集处理
通过分析每个事务的写集(即事务写入的行(表、键值)集)来检测事务依赖关系。如果两个事务修改相同的行,则它们被视为依赖关系,并且必须按顺序(换句话说,串行)应用,以避免死锁或不正确的结果。如果表具有辅助唯一键,这些值也会添加到事务的写集中,以检测由不同事务影响相同唯一键值而暗示事务依赖关系的情况,因此需要排序。无法有效确定依赖关系的情况下,mysqld会退回到考虑出于安全原因而依赖于事务的情况。
事务依赖关系通过源mysqld在二进制日志中编码。依赖关系通过使用称为“逻辑时钟”的方案在ANONYMOUS_GTID事件中编码。(参见 Section 19.1.4.1, “Replication Mode Concepts”.)
MySQL(和 NDB Cluster)采用的写集实现使用基于哈希的冲突检测,基于匹配的相关表和索引值的 64 位行哈希。这可靠地检测到当相同键被看到两次时,但如果不同的表和索引值哈希到相同的 64 位值,则可能产生误报,这可能导致人为依赖关系,从而降低可用的并行性。
事务依赖关系由以下任一方式强制:
-
DDL 语句
-
二进制日志轮换或遇到二进制日志文件边界
-
写集历史大小限制
-
写入引用目标表中的父外键
更具体地说,对外键父表执行插入、更新和删除的事务相对于所有前后事务进行序列化,而不仅仅是与涉及约束关系的表相关的事务。相反,对外键子表(引用)执行插入、更新和删除的事务与彼此之间并没有特别的序列化。
MySQL MTA 实现尝试并行应用独立的二进制日志事务。NDB记录在一个二进制日志事务中发生的所有用户事务在一个时期内提交的所有更改(TimeBetweenEpochs,默认为 100 毫秒)。因此,为了使两个连续的时期事务独立且可能并行应用,需要确保在两个时期中没有任何行被修改。如果任何单行在两个时期中都被修改,则它们是依赖的,并且按顺序应用,这可能限制可利用的并行性。
基于在时期内在源集群上修改的行集,但不包括传达时期元数据的生成的mysql.ndb_apply_status WRITE_ROW事件,时期事务被视为独立的。这避免了每个时期事务都简单地依赖于前一个时期,但需要在保留提交顺序的情况下在副本上应用 binlog。这也意味着具有写集依赖关系的 NDB 二进制日志不适合由使用不同 MySQL 存储引擎的副本数据库使用。
可能或有必要修改应用程序事务行为,以避免在短时间内通过单独的事务重复修改相同行的模式,以增加可利用的应用并行性。
写集跟踪内存使用
用于跟踪二进制日志事务写入集的内存使用量可以使用binlog_transaction_dependency_history_size服务器系统变量进行设置,默认为 25000 行哈希。
如果平均二进制日志事务修改了*N行,则为了能够识别独立(可并行化)事务达到并行级别P*,我们需要binlog_transaction_dependency_history_size至少为*N* * *P*。(最大值为 1000000。)
历史记录的有限大小导致可靠确定的有限最大依赖长度,从而给出可以表达的有限并行性。在历史记录中找不到的任何行可能依赖于从历史记录中清除的最后一个事务。
写入集历史不像是对最后*N*个事务的滑动窗口;相反,它是一个允许完全填满的有限缓冲区,当其完全填满时,其内容完全丢弃。这意味着历史大小随时间呈锯齿状变化,因此最大可检测的依赖长度也随时间呈锯齿状变化,这样,如果写入集历史缓冲区在它们被处理之间被重置,独立事务仍可能被标记为依赖。
在这个方案中,二进制日志文件中的每个事务都带有一个sequence_number(1、2、3,...),以及它依赖的最近二进制日志事务的序列号,我们称之为last_committed。
在给定的二进制日志文件中,第一个事务的sequence_number为 1,last_committed为 0。
如果一个二进制日志事务依赖于其直接前任,则其应用是串行的。如果依赖于较早的事务,则可能可以与前面的独立事务并行应用该事务。
ANONYMOUS_GTID事件的内容,包括sequence_number和last_committed(因此事务依赖关系),可以使用mysqlbinlog查看。
在源上生成的ANONYMOUS_GTID事件与批量BEGIN、TABLE_MAP*、WRITE_ROW*、UPDATE_ROW*、DELETE_ROW*和COMMIT事件的压缩事务有效载荷分开处理,允许在解压缩之前确定依赖关系。这意味着副本协调器线程可以将事务有效载荷解压缩委托给工作线程,从而在副本上自动并行解压缩独立事务。
已知限制
次要唯一列。 具有次要唯一列(即主键以外的唯一键)的表将所有列发送到源,以便可以检测到与唯一键相关的冲突。
当当前的二进制日志模式不包括所有列,而只包括更改的列(--ndb-log-updated-only=OFF, --ndb-log-update-minimal=ON, --ndb-log-update-as-write=OFF)时,这可能会增加从数据节点发送到 SQL 节点的数据量。
影响取决于这些表中行的修改(更新或删除)速率以及实际未修改的列中的数据量。
将 NDB 复制到 InnoDB。 NDB 二进制日志注入器事务依赖跟踪有意忽略由生成的 mysql.ndb_apply_status 元数据事件创建的事务间依赖关系,这些事件作为副本应用程序上的时代事务提交的一部分单独处理。对于复制到InnoDB,没有特殊处理;当使用InnoDB多线程应用程序来消耗NDB MTA 二进制日志时,这可能导致性能降低或其他问题。
25.7.12 NDB Cluster 复制冲突解决
原文:
dev.mysql.com/doc/refman/8.0/en/mysql-cluster-replication-conflict-resolution.html
-
要求
-
源列控制
-
冲突解决控制
-
冲突解决函数
-
冲突解决异常表
-
冲突检测状态变量
-
示例
在涉及多个源(包括循环复制)的复制设置中,可能会出现不同源尝试使用不同数据更新副本上相同行的情况。NDB Cluster 复制中的冲突解决提供了一种通过允许使用用户定义的解决列来确定是否应在副本上应用给定源上的更新来解决此类冲突的方法。
NDB Cluster 支持的一些冲突解决类型(NDB$OLD()、NDB$MAX()和NDB$MAX_DELETE_WIN();此外,在 NDB 8.0.30 及更高版本中,还有NDB$MAX_INS()和NDB$MAX_DEL_WIN_INS())将此用户定义列实现为“时间戳”列(尽管其类型不能是TIMESTAMP,如本节后面所述)。这些类型的冲突解决总是基于逐行而不是基于事务的。基于时代的冲突解决函数NDB$EPOCH()和NDB$EPOCH_TRANS()比较了复制时代的顺序(因此这些函数是事务性的)。在冲突发生时,可以使用不同的方法来比较副本上的解决列值,如本节后面所述;所使用的方法可以设置为在单个表、数据库或服务器上操作,或者使用模式匹配在一个或多个表上操作。有关在mysql.ndb_replication表的db、table_name和server_id列中使用模式匹配的信息,请参见使用通配符进行匹配。
您还应该记住,确保解析列正确填充相关值是应用程序的责任,以便解析函数在确定是否应用更新时可以做出适当选择。
要求
冲突解决的准备工作必须在源和副本上都进行。这些任务在以下列表中描述:
-
在写入二进制日志的源上,您必须确定要发送哪些列(所有列还是仅已更新的列)。这是通过在整个 MySQL Server 上应用mysqld启动选项
--ndb-log-updated-only(稍后在本节中描述)来完成的,或者通过在mysql.ndb_replication表中放置适当的条目来在一个或多个特定表上完成(参见 ndb_replication Table)。注意
如果您正在复制具有非常大列(如
TEXT或BLOB列)的表,--ndb-log-updated-only也可以用于减小二进制日志的大小,并避免由于超过max_allowed_packet而导致的可能的复制失败。有关此问题的更多信息,请参见 Section 19.5.1.20, “Replication and max_allowed_packet”。
-
在副本上,您必须确定要应用哪种冲突解决方法(“最新时间戳获胜”,“相同时间戳获胜”,“主要获胜”,“主要获胜,完成事务”或无)。这是通过使用
mysql.ndb_replication系统表来完成的,并适用于一个或多个特定表(参见 ndb_replication Table)。 -
NDB Cluster 还支持读冲突检测,即检测一个集群中对给定行的读取与另一个集群中对同一行的更新或删除之间的冲突。这需要通过在副本上将
ndb_log_exclusive_reads设置为 1 来获得独占读锁。所有被冲突读取的行都将被记录在异常表中。有关更多信息,请参见读冲突检测和解决。 -
在 NDB 8.0.30 之前,
NDB严格将WRITE_ROW事件应用为插入操作,要求不存在这样的行;也就是说,如果行已经存在,则传入的写操作总是被拒绝。(当使用除NDB$MAX_INS()或NDB$MAX_DEL_WIN_INS()之外的任何冲突解决函数时,仍然如此。)从 NDB 8.0.30 开始,当使用
NDB$MAX_INS()或NDB$MAX_DEL_WIN_INS()时,NDB可以对WRITE_ROW事件进行幂等应用,将这样的事件映射到插入操作,当传入行不存在时,或者当传入行存在时映射到更新操作。
当使用函数NDB$OLD(),NDB$MAX()和NDB$MAX_DELETE_WIN()进行基于时间戳的冲突解决(以及从 NDB 8.0.30 开始使用NDB$MAX_INS()和NDB$MAX_DEL_WIN_INS()),我们通常将用于确定更新的列称为“时间戳”列。然而,此列的数据类型从不是TIMESTAMP;相反,其数据类型应为INT - INTEGER, INT, SMALLINT, TINYINT, MEDIUMINT, BIGINT")(INTEGER - INTEGER, INT, SMALLINT, TINYINT, MEDIUMINT, BIGINT"))或BIGINT - INTEGER, INT, SMALLINT, TINYINT, MEDIUMINT, BIGINT")。 “时间戳”列还应为UNSIGNED和NOT NULL。
本节后面讨论的NDB$EPOCH()和NDB$EPOCH_TRANS()函数通过比较应用在主要和次要 NDB 集群上的复制时期的相对顺序来工作,并不使用时间戳。
源列控制
我们可以根据“之前”和“之后”图像来看待更新操作——也就是说,在应用更新之前和之后的表状态。通常,当使用主键更新表时,“之前”图像并不是很重要;然而,当我们需要根据每次更新确定是否在副本上使用更新的值时,我们需要确保两个图像都写入源二进制日志。这是通过--ndb-log-update-as-write选项为mysqld完成的,稍后在本节中描述。
重要
决定是记录完整行还是仅更新列是在启动 MySQL 服务器时完成的,无法在线更改;您必须重新启动mysqld,或者使用不同的日志记录选项启动新的mysqld实例。
冲突解决控制
冲突解决通常在可能发生冲突的服务器上启用。与日志记录方法选择一样,它是通过mysql.ndb_replication表中的条目启用的。
NBT_UPDATED_ONLY_MINIMAL和NBT_UPDATED_FULL_MINIMAL可与NDB$EPOCH()、NDB$EPOCH2()和NDB$EPOCH_TRANS()一起使用,因为这些不需要非主键列的“之前”值。需要旧值的冲突解决算法,如NDB$MAX()和NDB$OLD(),与这些binlog_type值不正确地配合。
冲突解决函数
本节提供了关于可用于 NDB 复制中冲突检测和解决的函数的详细信息。
-
NDB$OLD()")
-
NDB$MAX()")
-
NDB$MAX_DELETE_WIN()")
-
NDB$MAX_INS()")
-
NDB$MAX_DEL_WIN_INS()")
-
NDB$EPOCH()")
-
NDB$EPOCH_TRANS()")
-
NDB$EPOCH2()")
-
NDB$EPOCH2_TRANS()")
NDB$OLD()
如果源数据和副本上的*column_name*的值相同,则应用更新;否则,在副本上不应用更新,并将异常写入日志。以下是伪代码示例:
if (*source_old_column_value* == *replica_current_column_value*)
apply_update();
else
log_exception();
此函数可用于“相同值获胜”冲突解决。这种冲突解决确保更新不会从错误的源应用于副本。
重要提示
此函数使用源数据的“之前”图像中的列值。
NDB$MAX()
对于更新或删除操作,如果源数据中给定行的“时间戳”列值高于副本中的值,则应用该操作;否则不应用于副本。以下是伪代码示例:
if (*source_new_column_value* > *replica_current_column_value*)
apply_update();
此函数可用于“最大时间戳获胜”冲突解决。这种冲突解决确保在冲突发生时,最近更新的行版本是持久的版本。
除了拒绝具有与先前写操作相同主键的写操作外,此函数对写操作之间的冲突没有影响;仅当不存在使用相同主键的先前写操作时,才接受并应用该写操作。从 NDB 8.0.30 开始,您可以使用NDB$MAX_INS()")处理写操作之间的冲突解决。
重要
此函数使用源“after”图像的列值。
NDB$MAX_DELETE_WIN()
这是NDB$MAX()的变体。由于删除操作没有时间戳可用,因此使用NDB$MAX()进行的删除实际上被处理为NDB$OLD,但对于某些用例,这并不理想。对于NDB$MAX_DELETE_WIN(),如果来自源的添加或更新现有行的行的“时间戳”列值高于副本上的值,则应用该值。但是,删除操作始终被视为具有更高的值。如下伪代码所示:
if ( (*source_new_column_value* > *replica_current_column_value*)
||
*operation.type* == "delete")
apply_update();
此函数可用于“最大时间戳,删除获胜”冲突解决。这种冲突解决确保在冲突发生时,被删除或(其他方式)最近更新的行版本是持久的版本。
注意
与NDB$MAX()一样,此函数使用源“after”图像的列值。
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_INS()添加在 NDB 8.0.30 中。
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$MAX_DEL_WIN_INS() 添加在 NDB 8.0.30 中。
NDB$EPOCH()
NDB$EPOCH() 函数跟踪在副本集群上应用的复制时期的顺序,相对于在副本上发起的更改。这种相对顺序用于确定在副本上发起的更改是否与本地发起的任何更改同时发生,因此可能存在冲突。
在 NDB$EPOCH() 的描述中接下来的大部分内容也适用于 NDB$EPOCH_TRANS()。任何异常情况在文本中有注明。
NDB$EPOCH() 是不对称的,操作在一个 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()"))。你必须在创建要使用的数据表之前创建任何异常表。
与本节讨论的其他冲突检测函数一样,通过在mysql.ndb_replication表中包含相关条目来激活NDB$EPOCH()和NDB$EPOCH_TRANS()(参见 ndb_replication 表)。在这种情况下,主 NDB 集群和辅助 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 *(延迟+排队+TimeBetweenEpochs)成比例。这意味着——假设默认为TimeBetweenEpochs并忽略集群之间的任何延迟(以及任何排队延迟)——最小冲突窗口大小约为 200 毫秒。在查看预期应用程序“竞争”模式时,应考虑这个最小窗口。 -
使用
NDB$EPOCH()和NDB$EPOCH_TRANS()函数的表需要额外的存储空间;每行需要额外的 1 到 32 位空间,具体取决于传递给函数的值。 -
删除操作之间的冲突可能导致主要和次要之间的分歧。当同时在两个集群上删除一行时,冲突可以被检测到,但不会被记录,因为行已被删除。这意味着在任何后续重新对齐操作的传播过程中不会检测到进一步的冲突,这可能导致分歧。
删除应该外部串行化,或者路由到一个集群。或者,应该在这些删除和随后的任何插入事务中事务更新一个单独的行,以便跟踪冲突。这可能需要应用程序的更改。
-
当使用
NDB$EPOCH()或NDB$EPOCH_TRANS()进行冲突检测时,目前仅支持双向“主动-主动”配置中的两个 NDB 集群。 -
具有
BLOB或TEXT列的表目前不支持使用NDB$EPOCH()或NDB$EPOCH_TRANS()。
NDB$EPOCH_TRANS()
NDB$EPOCH_TRANS()扩展了NDB$EPOCH()函数。冲突使用“主要获胜”规则(参见 NDBEPOCH()在次要上重新对齐单个冲突行,而NDB$EPOCH_TRANS()`在冲突事务上重新对齐。
此外,任何可检测依赖于冲突事务的事务也被视为冲突,这些依赖关系由次要集群的二进制日志内容确定。由于二进制日志仅包含数据修改操作(插入、更新和删除),因此只有重叠的数据修改用于确定事务之间的依赖关系。
NDB$EPOCH_TRANS()受到与NDB$EPOCH()相同的条件和限制,并且还要求所有事务 ID 都记录在次要的二进制日志中,使用--ndb-log-transaction-id设置为ON。这会增加可变的开销量(每行最多 13 个字节)。
废弃的log_bin_use_v1_row_events系统变量,默认值为OFF,不应该与NDB$EPOCH_TRANS()一起设置为ON。
参见 NDB$EPOCH()")。
NDB$EPOCH2()
NDB$EPOCH2()函数类似于NDB$EPOCH(),不同之处在于NDB$EPOCH2()提供了双向复制拓扑的删除-删除处理。在这种情况下,通过在每个源上设置ndb_slave_conflict_role系统变量的适当值(通常一个PRIMARY,一个SECONDARY)来为两个源分配主要和次要角色。完成此操作后,次要所做的修改会由主要反映回次要,然后有条件地应用这些修改。
NDB$EPOCH2_TRANS()
NDB$EPOCH2_TRANS()扩展了NDB$EPOCH2()函数。冲突的检测和处理方式相同,并为复制集群分配主要和次要角色,但额外条件是在发生冲突的同一事务中更新的任何其他行也被视为冲突。也就是说,NDB$EPOCH2()在次要上重新调整单个冲突行,而NDB$EPOCH_TRANS()重新调整冲突事务。
NDB$EPOCH()和NDB$EPOCH_TRANS()使用每行指定的元数据,每个最后修改的时代,在主键上确定来自辅助的复制行更改是否与本地提交的更改同时发生;同时发生的更改被视为冲突,随后的异常表更新和辅助的重新对齐。当主键上的行被删除时,不再有任何最后修改的时代可用来确定任何复制操作是否冲突,这意味着不会检测到冲突的删除操作。这可能导致分歧,例如一个集群上的删除与另一个集群上的删除和插入同时发生;这就是在使用NDB$EPOCH()和NDB$EPOCH_TRANS()时删除操作只能路由到一个集群的原因。
NDB$EPOCH2()通过忽略任何删除-删除冲突的问题—在主键上存储有关已删除行的信息—并避免任何潜在的结果分歧。通过将成功应用并从辅助复制的任何操作反射回辅助,可以在辅助上重新应用由来自主键的操作删除的操作。
当使用NDB$EPOCH2()时,您应该记住,辅助会应用从主键删除的操作,直到通过反射操作恢复新行。理论上,辅助上的后续插入或更新与主键上的删除冲突,但在这种情况下,我们选择忽略这一点,并允许辅助“获胜”,以防止集群之间的分歧。换句话说,在删除后,主键不会检测到冲突,而是立即采纳辅助的后续更改。因此,随着辅助向最终(稳定)状态前进,辅助的状态可以重新访问多个先前提交的状态,并且其中一些可能是可见的。
您还应该意识到,将所有操作从辅助反射回主键会增加主键的 logbinary 日志大小,以及对带宽、CPU 使用率和磁盘 I/O 的需求。
应用在辅助上的反射操作取决于辅助上目标行的状态。可以通过检查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 集群支持三个额外的预定义可选列 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 是由 NDB 生成的 64 位值。此值可用于关联属于相同冲突事务的多个异常表条目,这些条目来自相同或不同的异常表。
附加的参考列,不是原始表的主键的一部分,可以命名为 *colname*$OLD 或 *colname*$NEW。 *colname*$OLD 引用更新和删除操作中的旧值,即包含 DELETE_ROW 事件的操作。 *colname*$NEW 可用于引用插入和更新操作中的新值,换句话说,使用 WRITE_ROW 事件、UPDATE_ROW 事件或两种事件的操作。如果冲突操作未为给定的非主键参考列提供值,则异常表行包含 NULL 或该列的定义默认值。
重要
当数据表设置为复制时,将读取 mysql.ndb_replication 表,因此在创建要复制的表之前,必须将对应于要复制的表的行插入到 mysql.ndb_replication 中。
冲突检测状态变量
几个状态变量可用于监视冲突检测。您可以通过Ndb_conflict_fn_epoch系统状态变量的当前值,查看自此副本上次从头启动以来,由NDB$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提供了自上次mysqld启动以来,由于“最大时间戳获胜”冲突解决而未在当前 SQL 节点上应用的行数。Ndb_conflict_fn_max_del_win提供了基于NDB$MAX_DELETE_WIN()结果的冲突解决已应用的次数。
NDB 8.0.30 及更高版本提供了Ndb_conflict_fn_max_ins,用于跟踪“更大时间戳获胜”处理已应用于写操作的次数(使用NDB$MAX_INS());由状态变量Ndb_conflict_fn_max_del_win_ins提供了“相同时间戳获胜”写操作处理已应用的次数(由NDB$MAX_DEL_WIN_INS()实现)。
自上次mysqld重新启动以来,由于“相同时间戳获胜”冲突解决而未应用的行数由全局状态变量Ndb_conflict_fn_old给出。除了递增Ndb_conflict_fn_old外,未使用的行的主键被插入到异常表中,如本节其他地方所述。
另请参阅 Section 25.4.3.9.3, “NDB Cluster Status Variables”。
示例
以下示例假定您已经有一个正常工作的 NDB Cluster 复制设置,如 Section 25.7.5, “Preparing the NDB Cluster for Replication” 和 Section 25.7.6, “Starting NDB Cluster Replication (Single Replication Channel)”") 中所述。
NDB$MAX() 示例。 假设你希望在表 test.t1 上启用“最大时间戳获胜”冲突解决,使用列 mycol 作为“时间戳”。可以通过以下步骤完成:
-
确保你已经使用
--ndb-log-update-as-write=OFF启动了源 mysqld。 -
在源上执行这个
INSERT语句:INSERT INTO mysql.ndb_replication VALUES ('test', 't1', 0, NULL, 'NDB$MAX(mycol)');注意
如果
ndb_replication表尚不存在,则必须创建它。请参阅 ndb_replication Table。将 0 插入到
server_id列中表示所有访问该表的 SQL 节点应该使用冲突解决。如果你只想在特定的 mysqld 上使用冲突解决,使用实际的服务器 ID。将
NULL插入到binlog_type列中与插入 0 (NBT_DEFAULT) 具有相同的效果;使用服务器默认值。 -
创建
test.t1表:CREATE TABLE test.t1 ( *columns* mycol INT UNSIGNED, *columns* ) ENGINE=NDB;现在,当对该表执行更新时,将应用冲突解决,并将具有
mycol最大值的行版本写入副本。
注意
其他 binlog_type 选项,如 NBT_UPDATED_ONLY_USE_UPDATE (6),应该使用 ndb_replication 表来控制源上的日志记录,而不是使用命令行选项。
NDB$OLD() 示例。 假设正在复制一个 NDB 表,如此处定义的表,并且你希望为更新到该表的“相同时间戳获胜”冲突解决启用:
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;我们可以为给定冲突的类型、原因和起始事务 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中的一个列,因此四个必需列需要使用NDB$前缀。 -
如前所示创建
test.t2表。
这些步骤必须针对每个要使用NDB$OLD()执行冲突解决的表进行遵循。对于每个这样的表,必须在mysql.ndb_replication中有一个相应的行,并且在被复制的表所在的同一数据库中必须有一个异常表。
阅读冲突检测和解决。 NDB Cluster 还支持跟踪读操作,这使得在循环复制设置中可以管理一个集群中给定行的读取与另一个集群中相同行的更新或删除之间的冲突。此示例使用employee和department表来模拟这样一种情况:在源集群(以下简称为集群A)中将员工从一个部门移动到另一个部门,而副本集群(以下简称为B)在交错事务中更新员工以前部门的员工计数。
数据表已使用以下 SQL 语句创建:
# Employee table
CREATE TABLE employee (
id INT PRIMARY KEY,
name VARCHAR(2000),
dept INT NOT NULL
) ENGINE=NDB;
# Department table
CREATE TABLE department (
id INT PRIMARY KEY,
name VARCHAR(2000),
members INT
) ENGINE=NDB;
两个表的内容包括以下SELECT语句的(部分)输出中显示的行:
mysql> SELECT id, name, dept FROM employee;
+---------------+------+
| id | name | dept |
+------+--------+------+
...
| 998 | Mike | 3 |
| 999 | Joe | 3 |
| 1000 | Mary | 3 |
...
+------+--------+------+
mysql> SELECT id, name, members FROM department;
+-----+-------------+---------+
| id | name | members |
+-----+-------------+---------+
...
| 3 | Old project | 24 |
...
+-----+-------------+---------+
我们假设我们已经在使用包含四个必需列(并且这些列用于此表的主键)的异常表,操作类型和原因的可选列,以及原始表的主键列,使用此处显示的 SQL 语句创建:
CREATE TABLE employee$EX (
NDB$server_id INT UNSIGNED,
NDB$source_server_id INT UNSIGNED,
NDB$source_epoch BIGINT UNSIGNED,
NDB$count INT UNSIGNED,
NDB$OP_TYPE ENUM( 'WRITE_ROW','UPDATE_ROW', 'DELETE_ROW',
'REFRESH_ROW','READ_ROW') NOT NULL,
NDB$CFT_CAUSE ENUM( 'ROW_DOES_NOT_EXIST',
'ROW_ALREADY_EXISTS',
'DATA_IN_CONFLICT',
'TRANS_IN_CONFLICT') NOT NULL,
id INT NOT NULL,
PRIMARY KEY(NDB$server_id, NDB$source_server_id, NDB$source_epoch, NDB$count)
) ENGINE=NDB;
假设在两个集群上发生了两个同时事务。在集群A上,我们创建一个新部门,然后将员工编号 999 移入该部门,使用以下 SQL 语句:
BEGIN;
INSERT INTO department VALUES (4, "New project", 1);
*UPDATE employee SET dept = 4 WHERE id = 999;* COMMIT;
与此同时,在集群B上,另一个事务从employee中读取,如下所示:
BEGIN;
*SELECT name FROM employee WHERE id = 999;* UPDATE department SET members = members - 1 WHERE id = 3;
commit;
冲突解决机制通常不会检测到冲突的事务,因为冲突是在读取(SELECT)和更新操作之间。您可以通过在复制集群上执行SET ndb_log_exclusive_reads = 1来解决此问题。以这种方式获取独占读锁会导致在源上读取的任何行在复制集群上被标记为需要冲突解决。如果在记录这些事务之前以这种方式启用独占读取,则在集群B上的读取将被跟踪并发送到集群A进行解决;随后检测到员工行上的冲突,并且在集群B上的事务被中止。
冲突在异常表(在集群A上)中注册为READ_ROW操作(参见冲突解决异常表,了解操作类型的描述),如下所示:
mysql> SELECT id, NDB$OP_TYPE, NDB$CFT_CAUSE FROM employee$EX;
+-------+-------------+-------------------+
| id | NDB$OP_TYPE | NDB$CFT_CAUSE |
+-------+-------------+-------------------+
...
| 999 | READ_ROW | TRANS_IN_CONFLICT |
+-------+-------------+-------------------+
在读操作中找到的任何现有行都会被标记。这意味着由于同一冲突导致的多行可能会在异常表中记录,如通过检查在同时事务中在集群A上进行更新和在集群B上从相同表读取多行之间的冲突的影响所示。在集群A上执行的事务如下所示:
BEGIN;
INSERT INTO department VALUES (4, "New project", 0);
*UPDATE employee SET dept = 4 WHERE dept = 3;* SELECT COUNT(*) INTO @count FROM employee WHERE dept = 4;
UPDATE department SET members = @count WHERE id = 4;
COMMIT;
同时,在集群B上运行包含以下语句的事务:
SET ndb_log_exclusive_reads = 1; # Must be set if not already enabled
...
BEGIN;
*SELECT COUNT(*) INTO @count FROM employee WHERE dept = 3 FOR UPDATE;* UPDATE department SET members = @count WHERE id = 3;
COMMIT;
在这种情况下,第二个事务的SELECT中匹配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 集群实例中执行独占行锁定的方式。
插入冲突检测和解决示例(NDB 8.0.30 及更高版本)。 以下示例说明了在 NDB 8.0.30 中添加的插入冲突检测功能的使用。我们假设我们正在复制数据库test中的两个表t1和t2,并且希望对t1使用NDB$MAX_INS()进行插入冲突检测,对t2使用NDB$MAX_DEL_WIN_INS()进行插入冲突检测。这两个数据表直到设置过程的后期才会创建。
设置插入冲突解决类似于设置其他冲突检测和解决算法,如前面的示例所示。如果用于配置二进制日志记录和冲突解决的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 Table。
您还可以为每个需要使用冲突解决的NDB表创建一个异常表。异常表记录由给定表的冲突解决函数拒绝的所有行。可以使用以下两个 SQL 语句为表t1和t2创建用于复制冲突检测的异常表:
CREATE TABLE `t1$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,
a INT NOT NULL,
PRIMARY KEY(NDB$server_id, NDB$master_server_id,
NDB$master_epoch, NDB$count)
) ENGINE=NDB;
CREATE TABLE `t2$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,
a INT NOT NULL,
PRIMARY KEY(NDB$server_id, NDB$master_server_id,
NDB$master_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, 'Source X=20', 20);
Query OK, 1 row affected (0.01 sec)
mysqlS> INSERT INTO t2 VALUES (2, 'Source 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, 'Slave X=30', 30);
Query OK, 1 row affected (0.01 sec)
mysqlR> INSERT INTO t2 VALUES (3, 'Slave 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$master_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$master_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$master_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$master_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)
正如我们之前所看到的,源端插入的其他行并没有被副本端拒绝,只有那些时间戳值较小于副本端冲突行的行被拒绝。
25.8 NDB Cluster 发行说明
NDB Cluster 发行版本的更改与本参考手册分开记录;您可以在 NDB 8.0 Release Notes 找到每个 NDB Cluster 8.0 版本更改的发行说明。
您可以从 NDB Cluster Release Notes 获取旧版本 NDB Cluster 的发行说明。
第二十六章 分区
目录
26.1 MySQL 中分区的概述
26.2 分区类型
26.2.1 范围分区
26.2.2 LIST 分区
26.2.3 列分区
26.2.4 HASH 分区
26.2.5 KEY 分区
26.2.6 子分区
26.2.7 MySQL 分区如何处理 NULL 值
26.3 分区管理
26.3.1 管理范围和列表分区
26.3.2 管理 HASH 和 KEY 分区
26.3.3 与表交换分区和子分区
26.3.4 分区的维护
26.3.5 获取有关分区的信息
26.4 分区修剪
26.5 分区选择
26.6 分区的限制和限制
26.6.1 分区键、主键和唯一键
26.6.2 与存储引擎相关的分区限制
26.6.3 与函数相关的分区限制
本章讨论用户定义的分区。
注意
表分区与窗口函数中使用的分区不同。有关窗口函数的信息,请参见第 14.20 节,“窗口函数”。
在 MySQL 8.0 中,分区支持由InnoDB和NDB存储引擎提供。
MySQL 8.0 目前不支持使用除InnoDB或NDB之外的任何存储引擎对表进行分区,例如MyISAM。尝试使用不提供本机分区支持的存储引擎创建分区表将失败,并显示ER_CHECK_NOT_IMPLEMENTED。
由 Oracle 提供的 MySQL 8.0 社区二进制文件包括由InnoDB和NDB存储引擎提供的分区支持。有关 MySQL 企业版二进制文件中提供的分区支持的信息,请参见第三十二章,MySQL 企业版。
如果您正在从源代码编译 MySQL 8.0,配置构建以支持InnoDB即可生成具有InnoDB表分区支持的二进制文件。有关更多信息,请参见第 2.8 节,“从源代码安装 MySQL”。
无需执行任何其他操作即可启用InnoDB的分区支持(例如,在my.cnf文件中不需要特殊条目)。
无法禁用InnoDB存储引擎的分区支持。
查看第 26.1 节,“MySQL 中分区概述”,介绍了分区和分区概念。
支持多种类型的分区,以及子分区;参见第 26.2 节,“分区类型”和第 26.2.6 节,“子分区”。
第 26.3 节,“分区管理”,介绍了在现有分区表中添加、删除和修改分区的方法。
第 26.3.4 节,“分区维护”,讨论了用于分区表的表维护命令。
INFORMATION_SCHEMA数据库中的PARTITIONS表提供有关分区和分区表的信息。有关更多信息,请参见第 28.3.21 节,“INFORMATION_SCHEMA PARTITIONS 表”;有关针对此表的一些查询示例,请参见第 26.2.7 节,“MySQL 分区如何处理 NULL”。
有关 MySQL 8.0 中分区的已知问题,请参见第 26.6 节,“分区的限制和限制”。
在处理分区表时,您可能还会发现以下资源很有用。
额外资源。 关于 MySQL 中用户定义分区的其他信息来源包括以下内容:
-
这是官方讨论论坛,供对 MySQL 分区技术感兴趣或正在尝试该技术的人使用。它包括来自 MySQL 开发人员和其他人的公告和更新。由分区开发和文档团队的成员监控。
-
一个 MySQL 新闻网站,提供与 MySQL 相关的博客,对于任何使用 MySQL 的人都应该感兴趣。我们鼓励您查看这里的链接,这些链接由使用 MySQL 分区的人维护,或者将您自己的博客添加到其中。
26.1 MySQL 中分区的概述
译文:
dev.mysql.com/doc/refman/8.0/en/partitioning-overview.html
本节提供了 MySQL 8.0 中分区的概念概述。
有关分区限制和功能限制的信息,请参见 第 26.6 节,“分区的限制和限制”。
SQL 标准在数据存储的物理方面并没有提供太多指导。SQL 语言本身旨在独立于其所使用的模式、表、行或列的任何数据结构或媒体而工作。尽管如此,大多数先进的数据库管理系统已经发展出一些确定特定数据存储的物理位置的方法,涉及文件系统、硬件甚至两者。在 MySQL 中,InnoDB 存储引擎长期以来一直支持表空间的概念(参见 第 17.6.3 节,“表空间”),即使在引入分区之前,MySQL 服务器也可以配置为使用不同的物理目录来存储不同的数据库(参见 第 10.12.2 节,“使用符号链接”,了解如何实现)。
分区通过使您能够根据您基本上可以根据需要设置的规则将个别表的部分分布到文件系统中,进一步发展了这一概念。实际上,表的不同部分以不同位置的单独表的形式存储。用户选择的数据划分规则称为分区函数,在 MySQL 中可以是模数、简单匹配一组范围或值列表、内部哈希函数或线性哈希函数。该函数根据用户指定的分区类型进行选择,并将用户提供的表达式的值作为其参数。该表达式可以是列值、作用于一个或多个列值的函数,或一个或多个列值的集合,具体取决于所使用的分区类型。
对于 RANGE、LIST 和 [LINEAR] HASH 分区,分区列的值将传递给分区函数,该函数返回表示应将该特定记录存储在哪个分区中的整数值。该函数必须是非常量且非随机的。它不能包含任何查询,但可以使用在 MySQL 中有效的 SQL 表达式,只要该表达式返回 NULL 或整数 intval,使得
-MAXVALUE <= *intval* <= MAXVALUE
(MAXVALUE 用于表示所讨论整数类型的最小上界。-MAXVALUE 表示最大下界。)
对于[LINEAR] KEY、RANGE COLUMNS和LIST COLUMNS分区,分区表达式由一个或多个列的列表组成。
对于[LINEAR] KEY分区,分区函数由 MySQL 提供。
有关允许的分区列类型和分区函数的更多信息,请参见第 26.2 节“分区类型”,以及提供分区语法描述和其他示例的第 15.1.20 节“CREATE TABLE 语句”。有关分区函数的限制信息,请参见第 26.6.3 节“与函数相关的分区限制”。
这被称为水平分区,即表的不同行可以分配给不同的物理分区。MySQL 8.0 不支持垂直分区,即表的不同列分配给不同的物理分区。目前没有计划将垂直分区引入 MySQL 中。
要创建分区表,必须使用支持它们的存储引擎。在 MySQL 8.0 中,同一分区表的所有分区必须使用相同的存储引擎。但是,没有任何阻止您在同一 MySQL 服务器上或甚至在同一数据库中为不同的分区表使用不同的存储引擎。
在 MySQL 8.0 中,仅支持分区的存储引擎是InnoDB和NDB。不能与不支持分区的存储引擎一起使用分区;这些包括MyISAM、MERGE、CSV和FEDERATED存储引擎。
使用NDB进行KEY或LINEAR KEY分区是可能的,但不支持使用其他类型的用户定义分区的表使用此存储引擎。此外,使用用户定义分区的NDB表必须具有显式主键,并且表的分区表达式中引用的任何列必须是主键的一部分。但是,如果在用于创建或修改用户分区的NDB表的PARTITION BY KEY或PARTITION BY LINEAR KEY子句中未列出任何列,则不需要该表具有显式主键。有关更多信息,请参见第 25.2.7.1 节“NDB Cluster 中的 SQL 语法不兼容”。
创建分区表时,默认使用与创建任何其他表时相同的存储引擎;要覆盖此行为,只需像为非分区表一样使用[STORAGE] ENGINE选项即可。目标存储引擎必须提供本机分区支持,否则语句将失败。需要记住,在CREATE TABLE语句中使用任何分区选项之前,需要将[STORAGE] ENGINE(以及其他表选项)列在之前。此示例显示了如何创建一个表,该表按哈希分区为 6 个分区,并使用InnoDB存储引擎(不管default_storage_engine的值是什么):
CREATE TABLE ti (id INT, amount DECIMAL(7,2), tr_date DATE)
ENGINE=INNODB
PARTITION BY HASH( MONTH(tr_date) )
PARTITIONS 6;
每个PARTITION子句可以包括一个[STORAGE] ENGINE选项,但在 MySQL 8.0 中,这没有任何效果。
除非另有说明,在本讨论中的其余示例假定default_storage_engine为InnoDB。
重要提示
分区适用于表的所有数据和索引;您不能仅对数据进行分区而不对索引进行分区,反之亦然,也不能仅对表的一部分进行分区。
每个分区的数据和索引可以使用DATA DIRECTORY和INDEX DIRECTORY选项分配到特定目录,这些选项是用于创建分区表的CREATE TABLE语句的PARTITION子句。
仅支持InnoDB表的各个分区和子分区的DATA DIRECTORY选项。截至 MySQL 8.0.21,DATA DIRECTORY子句中指定的目录必须为InnoDB所知。有关更多信息,请参阅使用 DATA DIRECTORY 子句。
表的分区表达式中使用的所有列必须是表可能具有的每个唯一键的一部分,包括任何主键。这意味着通过以下 SQL 语句创建的这样一个表不能进行分区:
CREATE TABLE tnp (
id INT NOT NULL AUTO_INCREMENT,
ref BIGINT NOT NULL,
name VARCHAR(255),
PRIMARY KEY pk (id),
UNIQUE KEY uk (name)
);
因为pk和uk键没有共同的列,所以没有列可用于分区表达式。在这种情况下的可能解决方法包括将name列添加到表的主键中,将id列添加到uk中,或者简单地完全删除唯一键。有关更多信息,请参阅第 26.6.1 节,“分区键、主键和唯一键”。
此外,MAX_ROWS和MIN_ROWS可用于确定每个分区中可以存储的最大和最小行数。有关这些选项的更多信息,请参阅第 26.3 节“分区管理”。
MAX_ROWS选项还可用于创建具有额外分区的 NDB Cluster 表,从而允许更大的哈希索引存储。有关DataMemory数据节点配置参数的文档,以及更多信息,请参阅第 25.2.2 节“NDB Cluster 节点、节点组、片段副本和分区”。
分区的一些优点列在这里:
-
分区使得可以在一个表中存储比单个磁盘或文件系统分区能够容纳的更多数据。
-
数据如果失去了其用处,通常可以通过删除仅包含该数据的分区(或分区)来轻松地从分区表中删除。相反,通过为存储特定数据添加一个或多个新分区,在某些情况下可以极大地促进添加新数据的过程。
-
由于满足给定
WHERE子句的数据只能存储在一个或多个分区中,因此某些查询可以在很大程度上进行优化,自动排除搜索中的任何剩余分区。由于分区表创建后可以更改分区,因此您可以重新组织数据以增强在设置分区方案时可能未经常使用的频繁查询。排除非匹配分区(以及它们包含的任何行)的能力通常被称为分区修剪。有关更多信息,请参阅第 26.4 节“分区修剪”。此外,MySQL 支持对查询进行显式分区选择。例如,
SELECT * FROM t PARTITION (p0,p1) WHERE c < 5仅选择与WHERE条件匹配的p0和p1分区中的行。在这种情况下,MySQL 不会检查表t的其他分区;当您已经知道要检查哪个分区或哪些分区时,这可以极大地加快查询速度。分区选择也支持数据修改语句DELETE、INSERT、REPLACE、UPDATE和LOAD DATA、LOAD XML。有关这些语句的更多信息和示例,请参阅这些语句的描述。