这篇文章是二阶段提交的 commit 子阶段的前奏,聊聊 commit 子阶段相关的一些概念。
作者:操盛春,爱可生技术专家,公众号『一树一溪』作者,专注于研究 MySQL 和 OceanBase 源码。
爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。
本文基于 MySQL 8.0.32 源码,存储引擎为 InnoDB。
1. 刷盘这件事
操作系统为文件提供了缓冲区,称为 page cache
。
程序写入内容到磁盘文件,实际上是先写入 page cache,再由操作系统把 page cache 中的内容写入磁盘文件。
把 page cache 中的内容写入磁盘文件这个操作,我们通常口语化的称为刷盘。
刷盘有两种方式:
- 一种是操作系统的后台线程异步刷盘。
- 另一种是程序主动触发操作系统的同步刷盘。
2. commit 阶段
二阶段提交的 commit 阶段,分为三个子阶段。
flush 子阶段,要干两件事:
第 1 件
,触发操作系统把 prepare 阶段及之前产生的 redo 日志刷盘。
事务执行过程中,改变(插入、更新、删除)表中数据产生的 redo 日志、prepare 阶段修改 undo 段状态产生的 redo 日志,都会由后台线程先写入 page cache,再由操作系统把 page cache 中的 redo 日志刷盘。
等待操作系统把 page cache 中的 redo 日志刷盘,这个时间存在不确定性,InnoDB 会在需要时主动触发操作系统马上把 page cache 中的 redo 日志刷盘。
上一篇文章,我们介绍过,二阶段提交的 prepare 阶段不会主动触发操作系统把 page cache 中的 redo 日志刷盘。这个刷盘操作会留到 flush 子阶段进行。
第 2 件
,把事务执行过程中产生的 binlog 日志写入 binlog 日志文件。
这个写入操作,也是先写入 page cache,至于操作系统什么时候把 page cache 中的 binlog 日志刷盘,flush 子阶段就不管了。
sync 子阶段,根据系统变量 sync_binlog 的值决定是否要触发操作系统马上把 page cache 中的 binlog 日志刷盘。
commit 子阶段,完成 InnoDB 的事务提交。
3. 组提交
flush 子阶段会触发 redo 日志刷盘,sync 子阶段可能
会触发 binlog 日志刷盘,都涉及到磁盘 IO。
TP 场景,比较常见
的情况是事务只改变(插入、更新、删除)表中少量数据,产生的 redo 日志、binlog 日志也比较少。
我们把这种事务称为小事务
。
以 redo 日志为例,一个事务产生的 redo 日志少,操作系统的一个页就有可能存放多个事务产生的 redo 日志。
如果每个事务提交时都把自己产生的 redo 日志刷盘,共享操作系统同一个页存放 redo 日志的多个事务,就会触发操作系统把这个页多次刷盘。
数据库闲的时候,把操作系统的同一个页多次刷盘,也没啥问题,反正磁盘闲着也是闲着。
数据库忙的时候,假设某个时间点有 1 万个小事务要提交,每 10 个小事务共享操作系统的一个页用于存放 redo 日志,总共需要操作系统的 1000 个页。
1 万个事务各自提交,就要触发操作系统把这 1000 个数据页刷盘 10000 次。
根据上面假设的这个场景,我们可以看到,这些事务都在某个时间点提交,可以等到共享操作系统同一个页的事务把 redo 日志都写入到 page cache 之后,再触发操作系统把 page cache 的这一个页刷盘。
这样一来,1000 个数据页,只刷盘 1000 次就可以了,刷盘次数只有原来的十分之一,效率大大的提高了。
上面是以 redo 日志为例描述操作系统的同一个页重复刷盘的问题,binlog 日志也有同样的问题。
某个时间点提交的多个事务触发操作系统的同一个页重复刷盘,这是个问题,为了解决这个问题,InnoDB 引入了组提交。
4. 队列和互斥量
组提交,就是把一组事务攒到一起提交,InnoDB 使用队列把多个事务攒到一起。
commit 阶段的 3 个子阶段都有自己的队列,分别为 flush 队列、sync 队列、commit 队列。
每个队列都会选出一个队长,负责管理这个队列,选队长的规则很简单,先到先得。
对于每个队列,第一个加入该队列的用户线程就是队长,第二个及以后加入该队列的都是队员。
代码里把队长称为
leader
,队员称为follower
。
我们生活中,不管当什么级别的队长,总会有点什么不一样,比如:有点钱、有点权、有点面子。
当选队长的用户线程,会有什么不一样吗?
这个当然是有的,队长有个特权,就是多干活。
每个子阶段的队长,都会把自己和所有队员在对应子阶段要干的事全都干了。队员只需要在旁边当吃瓜群众就好。
commit 子阶段有点不一样,到时候会介绍。
以 flush 子阶段为例,我们假设 flush 队列的队长为 A 队长
,A 队长收编一些队员之后,它会带领这帮队员从 flush 队列挪走,并且开始给自己和所有队员干活,队员们就在一旁当吃瓜群众。
A 队长带领它的队员挪走之后,flush 队列就变成空队列了。
接下来第一个进入 flush 队列的用户线程,又成为下一组的队长,我们称它为 B 队长
。
A 队长正在干活,还没干完呢。B 队长收编了一些队员之后,也带领这帮队员从 flush 子阶段的队列挪走,并且也要开始给自己和所有队员干活了。
如果 A 队长和 B 队长都把自己和各自队员产生的 binlog 日志写入 binlog 日志文件,相互交叉写入,那是会出乱子的。
为了避免 flush 子阶段出现两个队长同时干活导致出乱子,InnoDB 给 flush 子阶段引入了一个互斥量,名字是 LOCK_log
。
sync 子阶段、commit 子阶段也需要避免出现多个队长同时干活的情况,这两个子阶段也有各自的互斥量,分别是 LOCK_sync
、LOCK_commit
。
5. 总结
二阶段提交的 commit 阶段分为三个子阶段:flush 子阶段、sync 子阶段、commit 子阶段。
flush 子阶段会把 prepare 阶段及之前产生的 redo 日志都刷盘,把事务执行过程中产生的 binlog 日志写入 binlog 日志文件。
sync 子阶段会根据系统变量 sync_binlog 的值决定是否把 binlog 日志刷盘。
为了避免每个事务各自提交,触发操作系统对同一个页频繁的重复刷盘,InnoDB 引入了组提交。
为了避免每个子阶段出现多个队长同时干活的情况,InnoDB 还引入了三个互斥量:LOCK_log、LOCK_sync、LOCK_commit。
本期问题:关于本期内容,如有问题,欢迎留言交流。
下期预告:MySQL 核心模块揭秘 | 09 期 | 二阶段提交 (3) flush、sync、commit 子阶段。
更多技术文章,请访问:https://opensource.actionsky.com/
关于 SQLE
SQLE 是一款全方位的 SQL 质量管理平台,覆盖开发至生产环境的 SQL 审核和管理。支持主流的开源、商业、国产数据库,为开发和运维提供流程自动化能力,提升上线效率,提高数据质量。
SQLE 获取
类型 | 地址 |
---|---|
版本库 | https://github.com/actiontech/sqle |
文档 | https://actiontech.github.io/sqle-docs/ |
发布信息 | https://github.com/actiontech/sqle/releases |
数据审核插件开发文档 | https://actiontech.github.io/sqle-docs/docs/dev-manual/plugins/howtouse |