MySQL事务提交的时候,需要同时完成redo log和binlog的提交,为了保证两个日志的一致性,需要用到两阶段提交(与分布式的两阶段提交不同,这里的两阶段提交是发生在数据库内部)
假设执行一条SQL语句:
update T set c=c+1 where ID=2;
流程如下图所示(图片来自MySQL实战45讲):
两阶段流程
从图中可以看出,在最后提交事务的时候,需要有3个步骤:
假设当前 ID=2 的行,字段 c 的值是 0,再假设执行 update 语句过程中在写完第一个日志后,第二个日志还没有写完期间发生了 crash,会出现什么情况呢?
可以看到,如果不使用“两阶段提交”,那么数据库的状态就有可能和用它的日志恢复出来的库的状态不一致。
流程中崩溃可能导致问题如下图:
崩溃恢复
如果在图中时刻 A 的地方,也就是写入 redo log 处于 prepare 阶段之后、写 binlog 之前,发生了崩溃(crash),由于此时 binlog 还没写,redo log 也还没提交,所以崩溃恢复的时候,这个事务会回滚。这时候,binlog 还没写,所以也不会传到备库。
如果 redo log 里面的事务是完整的,也就是已经有了 commit 标识,则直接提交;如果 redo log 里面的事务只有完整的 prepare,则判断对应的事务 binlog 是否存在并完整:
这里,时刻 B 发生 crash 对应的就是 2(a) 的情况,崩溃恢复过程中事务会被提交。
ps: 两阶段提交的最后一个阶段的操作本身是不会失败的,除非是系统或硬件错误,所以也就不再需要回滚(不然就可以无限循环下去了)
分布式的两阶段提交,也存在崩溃恢复和重复提交的问题,如果commit阶段有的节点超时,就需要重新发出请求直到收到成功的回复,否则就协调者就标记该事务未完成。
问题:这个时候协调者可以继续接收新的事务吗?