来源:MySQL数据库联盟
上周,一个星球的伙伴问了一个问题:
一主两从,一个从库异步, 一个从库半同步的情况,主等半同步复制的ack的期间,那么异步复制的从库,有没有可能读到比主库更加新的数据?
这个问题,上周放到了我们的知识星球,大家也展开了讨论。
所以这篇文章也是算大家一起创作的,感谢:
@InnerCodeDBA
@李努力
@无与伦比
@伴夏
@sky
@仲景
@大番薯
这里就来总结一下这个问题。
当然,要说清这个问题,得先从两阶段提交说起:
两阶段是什么?
第一阶段:
写Redo Log,进入Prepare阶段。
第二阶段:
写Binlog
Redo Log 的Commit
两阶段提交的作用
再来看几种复制类型:
MySQL异步复制

异步复制的大致过程如下:
SQL解析:解析客户端发来的SQL;
存储引擎处理:存储引擎执行 SQL 操作;
写Redo Log(Prepare)阶段:事务的更改被准备好(prepare),但还没有提交到数据库;
写binlog:记录操作的Binlog;
传到从的中继日志:把 binlog 里面的内容传给从库的中继日志(relay log)中;
Redo Log提交:事务在InnoDB存储引擎中被提交。这时,更改正式应用到数据库文件中;
主库确认提交:主库在不等待从库确认的情况下向客户端返回commit成功;
从库应用更改:从库的 SQL 线程负责读取它的 relay log 里的信息并应用到从库数据库中。
在异步复制下,假如配置了自动切换的前提下,主库突然宕机,然后从提升为主时,原来主库上可能有一部分已经完成提交的数据还没来得及发送到从库,就可能产生数据丢失。为了解决这个问题,在 MySQL 5.5 版本中引入了半同步复制。下面来看下半同步复制的原理。
传统半同步复制after_commit

半同步复制的大致过程如下:
SQL解析
存储引擎处理:执行SQL操作
写Redo Log(Prepare)阶段:事务的更改被准备好(prepare),但还没有提交到数据库;
写binlog:记录操作的Binlog;
把Binlog里的内容传给从库的中继日志;
Redo Log提交:事务在InnoDB存储引擎中被提交。这时,更改正式应用到数据库文件中;
从库发送 ACK:从库收到 binlog 后,发送给主库一个 ACK,表示收到了
主库确认提交:主库收到这个 ACK 以后,才向客户端返回 commit 成功;
跟传统异步复制相比,半同步复制保证了所有客户端发送过确认提交的事务,从库都已经收到这个日志了。
但是这种模式下,实际上主库已经将该事务 commit 到事务引擎层,只是在等待返回而已,而此时其他 session 已经可以看到数据发生了变化,如果此时主库宕机,有可能从库还没写 Relay log,就会发生其他 session 切换前后查询的数据不一致的情况。
增强半同步复制after_sync

增强半同步复制after_sync的大致流程:
SQL解析
存储引擎处理:执行SQL操作,但不提交事务
写Redo Log(Prepare)阶段:事务的更改被准备好(prepare),但还没有提交到数据库;
写binlog
把Binlog里的内容传给从库的中继日志
等待从库确认:等待从库成功接收binlog的返回ACK信号
Redo Log提交:在收到从库的 ACK 信号后,事务在InnoDB存储引擎中被提交。这时,更改正式应用到数据库文件中;
反馈至客户端:确认提交后向客户端返回成功信息
问题解析
回到上面的问题
一主两从,一个从库异步, 一个从库半同步的情况,主等半同步复制的ack的期间,那么异步复制的从库,有没有可能读到比主库更加新的数据?
我们也讨论了很久,这里就只贴出@sky 大佬给到的最终答案:
假如半同步复制是after_commit
主库InnoDB层已经提交,其他数据库连接已经可以看见等待ack返回的事务结果了,即使异步从库也可以看见,也不算是看到比主库新的数据。
假如半同步复制是after_sync
Binlog已经落盘并且可以传输并回放到异步从库,但由于主库InnoDB层未能提交事务,主库其他数据库连接无法查看。此时异步从库有可能会查看到比主库更新的数据。
@InnerCodeDBA 针对这个问题,也画了一张图,方便我们理解:

为什么要讨论这个问题呢?后面又问了提问者,他说是业务上的一个真实场景:
有一套MySQL,主(A)从(B)之间是半同步复制,另外还有一个DTS监听主库的Binlog,同步到C。
但是开发遇到这样一个问题,先查C,再查主A,发现读到了旧数据,很多人会觉得:
既然DTS监听到Binlog,那可能事务已经提交了,为什么还会读到旧数据?(其实这是一种误解)
这种场景跟上面的分析也基本一致,因为DTS采用的是异步复制。
