mysql的两阶段提交

3 阅读4分钟

细节太多,抓主线说说.

默认前置知识:

  • binlog: 逻辑日志.三种格式:STATEMENT(记录sql语句),ROW(行变化),MIX(混合模式,由mysql自动判断使用STATEMENT还是ROW格式)

  • binlog cache: 线程(连接)私有,每个线程在执行事务过程中,将binlog暂存binlog cache,事务提交时,根据参数sync_binlog决定是否将数据刷盘.

  • redo log : 物理日志,描述对一个页具体的物理修改.

  • redo log buffer: 全局共享,事务执行过程中产生的redo log都暂存redo log buffer,不同事务产生的redo log可穿插写入redo log buffer但要加锁顺序写.

  • mtr(mini-transaction): 写入redo log的原子单元. 一条写操作,可能会产生多个mtr,而一个mtr可能会生成一组连续的redo log.所以,写redo log buffer实际是以mtr为单元,以LSN的递增保障事务中多个写操作的顺序.

两个配置参数:

1.sync_binlog:决定是否将page cache中的数据强制刷到磁盘(只控制fsync)

  • sync_binlog=1:事务提交后,立即执行fsync;
  • sync_binlog=0:不主动fsync,由操作系统决定何时刷盘(崩溃会丢事务)
  • sync_binlog=N:每N次事务提交执行一次fsync.

binlog cache是连接私有且连接复用,事务提交时,write操作总是发生(无论sync_binlog如何配置),事务回滚会清空binlog cache; 每次事务结束后会重置binlog cache(写指针归零,标记为空闲)

2.innodb_flush_log_at_trx_commit:决定是否将redo log buffer中数据write到page cache,再fsync到磁盘.(同时控制write和fsync)

  • innodb_flush_log_at_trx_commit=1:事务提交时,执行write并同步执行fsync;
  • innodb_flush_log_at_trx_commit=2: 事务提交时,只执行write,不执行fsync(每秒执行一次)
  • innodb_flush_log_at_trx_commit=0: 每秒执行一次write并fsync(不保证每个事务持久化)

sync_binlog和innodb_flush_log_at_trx_commit同时等于1, 也就是双1配置.

好,前置知识整理完,直奔主题.

两阶段提交,mysql为了保障redo log和binlog之间数据一致性,将一次事务提交拆分成pepare和commit 两个阶段.

两阶段提交: (innodb存储引擎,RR隔离级别)

执行sql:

1数据页操作

innodb引擎将数据页从磁盘加载到BufferPool.(如果数据页不在BufferPool),修改数据,此时数据页变成脏页.

2,写redo log buffer

innodb引擎将这次sql执行产生mtr写入redo log buffer.

3,写binlog cache

server层将逻辑变更写入binlog cache.

事务提交:

1,server层调用innodb的接口

innodb将redo log buffer中的数据,根据innodb_flush_log_at_trx_commit参数配置,刷到redo log文件,并将该事务标记为prepare状态.

2,server层将binlog cache中的数据,调用write写入page cache ,根据sync_binlog参数配置,决定是否调用fsync将数据刷到binlog文件.

3,server层调用innodb的commit方法

innodb在redo log buffer中,追加一条commit日志(通常是不强制刷盘的),并不会修改之前的prepare记录. commit日志可能会跟随下次事务提交时刷盘,或者,根据innodb_flush_log_at_timeout参数配置,由后台线程刷盘.

最后,释放锁,清理资源,返回客户端. 以上就是两阶段提交的过程.

那么经典问题,为什么需要两阶段提交?

这个问题的核心在于,mysql的主从复制依赖binlog,而崩溃恢复依赖redo log. 这俩不一致,崩溃恢复后主从库数据就不一致,那没办法保障业务正确流转了.

先看看如果不使用两阶段提交,会有什么问题?

1,先写binlog,后写redo log

事务T执行,server层先写binlog落盘,innodb写redo log之前mysql崩溃, 重启时,mysql根据redo log恢复事务修改,redo log中根本没有事务T的修改, 但是在主从复制时,从库读取主库的binlog, binlog中包含事务T,从库重放,导致从库比主库多一条数据.

2,先写redo log ,后写binlog

事务T执行,innodb修改数据,写redo log并落盘,在server层写binlog前,mysql崩溃,重启时,mysql根据redo log恢复事务T修改,主库数据已变更. 从库读取主库binlog,事务T记录缺失, 主从库数据不一致 .同时基于binlog的备份恢复也会丢失该事务.

那么两阶段可以很好的解决这种问题,

Prepare 阶段:InnoDB 写入redo log buffer按配置刷盘,Server 层写入 binlog cache按配置刷盘,并将事务标记为prepare状态.

Commit 阶段:InnoDB 将 redo log 中该事务的状态改为 commit(通常不刷盘)。

崩溃恢复规则

  • 如果崩溃时 redo log 为 prepare 状态,但 binlog 中没有该事务的记录:回滚该事务。
  • 如果崩溃时 redo log 为 prepare 状态,且 binlog 中该事务的记录:提交该事务。
  • 如果 redo log 已经是 commit 状态:直接提交。

好,到此就是完整的两阶段提交流程.

下次分享redo log 和undo log在崩溃恢复时是如何配合的.