MySQL的两种主从复制方式

  • 基于二进制日志文件偏移量(Binary Log File Position)
  • 基于 GTID(Global Transaction Identifiers)

基于二进制日志文件偏移量

主库的所有变更操作(写入更新)都会视为事件,被写入二进制日志文件中。从库通过读取主库的二进制日志文件,并在从库中执行这些事件,达到主从同步。

默认情况下,从库会执行由主库读取到的所有日志事件,即全数据库复制;也可以通过配置使从数据库只执行某些特定的数据库或数据表的日志事件,即部分数据库表复制。

每个从库都会保留一个记录,包括二进制日志文件名和偏移量,从库会记录哪些来自主库的日志事件已经在从库执行过。这意味着多个从库可以连接同一个主库,并执行同一个二进制日志文件的不同部分,从库处理从主库复制的过程,因此从库不论连接与否都不会影响主库的操作。同时,每个从库都会记录当前已经执行日志事件的位置,从库就可以在断开连接后再重连的时候,能够从记录的位置继续从主库同步。

基于 GTID

GTID 是一种基于数据库事务的复制方式,当使用 GTID 时,所有数据库事务都会被打上标记,并能在主库上跟踪到该提交(commit),然后在任一从库执行。

什么是 GTID

GTID(Global Transaction Identifiers)是对一个已提交事务且全局唯一的一个编号。GTID 实际上是由 UUID+TID 组成,其中 UUID 是一个 MySQL 实例的唯一标识,而 TID 则是该实例上已经提交的事务数量,并且会随着事务的提交不断递增。下面是 GTID 的具体形式:

1dca6e5e-adec-11e9-ba67-0242c0a86004:1-5

冒号前部分为 MySQL 实例的唯一标识,可以使用 SHOW GLOBAL VARIABLES LIKE '%server_uuid%' 查看当前MySQL实例的唯一标识。冒号后则是上述所说的 TID。

GTID 集合可以包括来自多个 MySQL 实例的事务,它们之间用逗号分隔。如果来自同一 MySQL 实例的事务序号有多个范围区间,各组范围之间用冒号分隔。例如:

9a8d9e2b-add8-11e9-cc35-0242c0a85003:1-3:8-10,

61f70873-add8-11e9-95e1-0242c0a83002:1-25

可以使用 SHOW MASTER STATUS 查看当前实例的事务执行数。

为什么需要 GTID

在基于二进制日志的复制中,从库需要告知主库要从哪个偏移量进行增量同步,主库通过给定的偏移量开始发送事件。如果偏移量指向错误会造成数据的遗漏,从而造成数据的不一致。当复制出现错误或某主节点宕机,由于同一个事务在每个节点上所在 binlog 的名字和位置都不尽相同,那么从库重连并重新开始从主库进行同步时,如何找到当前从库同步点对应另一个主库节点上二进制日志文件和偏移量就成为了难题。

GTID 是完全基于数据库事务的,和所有的事务提交到主库一样,事务同样会提交到从库。借助 GTID,当启动一个新的从库或主库失效切换时,其它从库可以自动在新主库上找到正确的复制位置,不需要人工寻找复制位置,减少了人为设置复制位置发生误操作的风险。

使用 GTID 后,默认情况下主库将一个事务记录到二进制日志文件时,首先会记录它的 GTID,并将 GTID 和事务的相关信息一并发送到从库,由从库在本地应用执行,这个过程不会改变原有的事务 ID。因此在 GTID 的架构上,即使复制 N 层架构,事务ID仍然不会改变,有效保证数据完整性和一致性。由此也能得知,GTID 的复制方式是针对传统二进制日志文件偏移量复制进行改进的,它仍要通过写入二进制日志文件来同步,只是通过 GTID 进一步保证同步过程中的数据一致性。由于同一事务的 GTID 在所有主从节点上的值一直,根据从库当前同步停止点的GTID就能唯一定位到另一主节点的 GTID。MASTER_AUTO_POSITION 的出现,我们甚至不需要知道 GTID 的具体值。

GTID 可以使用基于 SQL 语句的复制(SBR),也可以使用基于行的复制(RBR),但为了最佳效果,官方推荐使用基于行的复制。

GTID 可以允许我们追溯任一被应用到从库上的事务的来源,即事务最初在哪个实例上提交的。一旦一个带有 GTID 的事务在某个数据库提交,后续所有具有同样 GTID 的事务将会在这个数据库被忽略。因此,一个提交到主库的事务不会在从库被应用多次,减少了数据发生不一致的风险。

如何产生 GTID

GTID 的生成是受 gtid_next 控制的。在主库上 gtid_next 默认是 AUTOMATIC,即每次事务提交时都会自动生成新的 GTID。它从当前已执行 GTID 集合(gtid_executed)中,找到一个大于0的未使用的最小值作为下一个事务 GTID。同时在 binlog 更新事务事件前会自动插入一条 set gtid_next 事件。

什么是 server-uuid

MySQL 5.6 后用 128 位的 server-uuid 代替了原本的 32 位 server-id 的大部分功能。原因很简单,server-id 依赖于配置文件的手工配置,有可能产生 ID 冲突,而自动生成 128 位 UUID 的算法可以保证所有的 MySQL UUID 都不会冲突。但配置时,仍需要在配置文件中配置不同的 server-uuid。

MySQL 第一次安装完成并初始化时,会在数据目录下创建一个 auto.cnf 文件用来保存自动生成的 server-uuid,如下:

$ cat /var/lib/mysql/auto.cnf
[auto]
server-uuid=61f70873-add8-11e9-95e1-0242c0a83002

当 MySQL 再次启动时会读取 auto.cnf 文件,继续使用上次生成的 server_uuid。