MySQL 5.6 全局事务 ID(GTID)实现原理(三)
这是 MySQL 5.6 全局事务 ID(GTID) 系列的第三篇博客。
?在之前的两篇博客中,第一篇? 介绍了全局事务 ID 的定义与数据结构。第二篇? 介绍了 MySQL 5.6 新增的全局事务状态(Gtid_state)。?这里准备介绍的是全局事务 ID 如何参与 MySQL 的主备复制流程。?MySQL 5.6 引入全局事务 ID 的首要目的,是保证 Slave 在复制的时候不会重复执行相同的事务操作;其次,是用全局事务 IDs 代替由文件名和物理偏移量组成的复制位点,定位 Slave 需要复制的 binlog 内容。?因此,MySQL 必须在写 binlog 时记录每个事务的全局 GTID,保证 Master / Slave 可以根据这些 GTID 忽略或者执行相应的事务。在实现上,MySQL 没有修改旧的 binlog 事件,而是新增了两类事件:?+----------------------------+----------------------------------------+| 名称 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?| 功能 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?|+----------------------------+----------------------------------------+| Previous_gtids_log_event | 该事件之前的全局事务 ID 集合。 ? ? ? ? ?|+----------------------------+----------------------------------------+| ? ? ? ? ? ? ? ? ?Gtid_log_event | 标记之后的事务对应的全局事务 ID。 ? ?|+----------------------------+----------------------------------------+?Gtid_log_event?在?MySQL 5.6 的 binlog 文件中,每个事务的开始不是 "BEGIN" ,而是 Gtid_log_event 事件:?
?
Request = { server_id, binlog_name, binlog_offset, gtids_executed } ??如果采用 auto_position 方式连接 Master,现在 Slave?发送的 binlog_name 和 binlog_offset 都是空白,Master 只使用 gtids_executed 定位 Slave 上需要执行的 binlog。?实现逻辑是这样的:Master 从第一个文件开始读取 binlog,逐个检查 Gdit_log_event 事件的全局事务 ID 是不是包含在 Slave 发送的 gtids_executed 集合中。如果发现这个 GTID 已经包含在 gtids_executed 集合内,就忽略后面的整段事务,不向 Slave 发送 binlog 内容。?其实这个过程还不是很优化,因为如果是正常情况,Master 需要遍历若干 G 的 binlog 才能找到 Slave 需要复制的 binlog 内容 —— 这应该是一个改进点。?全局事务 ID 与并发复制?MySQL 5.6 主备复制的另一个改变,是实现了多线程并行复制。这个功能必须有全局事务 ID 的支持,原因是:?1) 在并行复制方式下,有些操作是不按照记录在 binlog 中的顺序执行的。这样的话,如果按照文件名 + 物理偏移量的方式记录复制位点,则停止 / 恢复主备复制时,可能会有一些操作被重复执行。?2) 我们知道,即使是 Mixed / Row 模式下记录的 binlog,仍有些 DDL 操作是用 Statement 的方式编码的,这些 DDL 操作不能在 Slave 重复执行(因为非幂等)。一旦操作在 Slave 执行出错,结果就是复制中断。?因此,Slave 必须依赖 binlog 中的全局事务 ID,在停止 / 恢复主备复制时,精确的记录哪些事务在 Slave 执行过,哪些没有。?现在,MySQL 5.6 可以用 COM_BINLOG_DUMP_GTID 来保证这一点:在恢复主备复制时,Slave 向 Master 发送自己所有执行过的 GTIDs(logged_gtids),在上次中断主备复制时,已经执行过的 binlog 被 Master 直接滤掉,不向 Slave 传送。?总结?在主备复制上,MySQL 5.6 新增了三个特性:?1)使用 GTIDs 作为主备复制的位点,在写 binlog 时用 Gtid_log_event 标记事务。?2)支持 auto_position 方式进行主备切换。在新增的协议中,使用 GTIDs 作为复制位点向主库请求 binlog 信息。?3)多线程并发复制,使用 GTIDs 防止事务重复执行。?全局事务 ID(GTID)可以很好的支持这几个功能。而且,使用 GTIDs 避免了在传送 binlog 逻辑上依赖文件名和物理偏移量,能够更好的支持自动容灾切换。?但是个人感觉,全局事务 ID 这里还有些待解决的问题:?1)GTID 是局部有序的,不能记录事务的全局顺序。因此在双写 / 快速主备切换场景下,不能根据 GTID 顺序来解决更新冲突的问题。?2)容灾切换时,MASTER_AUTO_POSITION?只能解决记录位点的问题。为了保证一致性,停写和等待主备 Caught up 仍然是必须的,通常这是服务无法快速恢复的主要原因。?补充:参考资料?这篇博客用到的参考资料:?MySQL 5.6 Manual:Replication with Global Transaction Identifiers(link?)??WL#4677: Unique Server Ids for Replication Topology (UUIDs)??(link?)
WL#3584: Global Transaction Identifiers (GTIDs)(link?)??
顺便提下,MySQL Worklog? 是个好地方,你可以从这里了解 MySQL 的原始需求,开发人员的想法,还有潜在问题。?