![3c02bac8bdc68dd7d8e9463bc69c2dcc.png](https://i-blog.csdnimg.cn/blog_migrate/6f0b9b72522ca4405d56d4b287215460.jpeg)
背景
在已知环境和认知下,更新热点数据时,after_sync性能会很明显不如after_commit。同事认为,after_sync模式下,等待从库ACK时,会持有更新记录的行级锁;与此相反,after_commit模式下,因为等待ACK时,事务已经提交,不再持有事务内锁;这意味着,每个事务持有锁的时间延长了一段ACK时间,写入耗时一定会随着事务之间的锁竞争频繁度与ACK耗时体现出来。然而,令人意外的是,在另一位同事,做压测时,测试结果又打脸了,在无热点数据时,after_sync性能依然不如after_commit。因此,又一次引来了笔者的探索与求证欲望。
源码分析
根据笔者之前的文章《金融级MySQL切换卡主源码剖析》,找到LOCK_commit互斥锁,以此为入口,在源码中搜索“LOCK_commit”,迅速找到sql/http://binlog.cc的9570行,关键代码如下,
很显然,after_sync一定是在sync队列之后,commit队列之前。而进入commit队列的必要条件是一定要获取LOCK_commit互斥锁,故after_sync参数的处理一定就在附近。
9568
依次往下看,很快,就可以在9581行看到call_after_sync_hook函数的调用。顾名思义,很容易让人联想到after_sync,鉴于文章《金融级MySQL切换卡主源码剖析》的分析经验。找到了call_after_sync_hook函数,就开始跟随笔者一步步分析下去吧。在函数中找到RUN_HOOK调用,一步步拆解如下,
// 调用方式
找到观察器调用宏FOREACH_OBSERVER定义,
观察者模式请自行了解,因为笔者非正统开发人士,理解有限
#define FOREACH_OBSERVER(r, f, thd, args)
到此,可以看到,这是在遍历所有的观察者。从observer_info_iter找出观察者基类Delegate。接着,找到具有after_sync函数实现的子类Binlog_storage_delegate,其observer为Binlog_storage_observer。而Binlog_storage_observer定义如下,
// 接口定义
而repl_semi_report_binlog_sync函数内容分析如下,
if
以上代码,我们可以看到,commitTrx函数(即ReplSemiSyncMaster::commitTrx函数实现了半同步的ACK等待)。很显然,我们可以大胆猜测,在after_commit模式也会调用这个函数。在代码中搜索commitTrx,很快可以找到函数repl_semi_report_commit,如下,
97
从rpl_semi_sync_master_wait_point就可以很清楚的确认,after_commit模式,也是调用此函数。而现在,我们需要确认的是,after_commit模式,在什么时候去调用ACK等待。
根据以上的查找逻辑,依葫芦画瓢,反查:从trans_observer中找到rpl_semi_sync_master_wait_point函数的接口定义。接着,去找它的观察者子类实现接口,找到Trans_delegate子类,实现函数接口为Trans_delegate::after_commit。而该函数的调用,根据上面的分析经验,我们可以很容易地拼接调用的方式,如下:
RUN_HOOK(tran, after_commit, (queue_head, log_file, pos))
以此,搜索代码,找到调用处。鉴于笔者搜索经验,首先搜索“RUN_HOOK(tran”很快就找了真正调用方式,如下:
(
有以下3次调用:
- MYSQL_BIN_LOG::process_after_commit_stage_queue函数
用于开启opt_binlog_order_commits时,刚好处理完commit队列,释放LOCK_commit互斥锁后。
- MYSQL_BIN_LOG::finish_commit函数
用于处理flush队列、sync队列以及commit队列处理异常的情况,暂不考虑
- ha_commit_low函数
事务提交接口,包括Binlog提交与InnoDB存储引擎层提交。针对组提交的正常情况,此处的after_commit逻辑也不会被调用。
流程梳理
根据以上的源码分析,流程梳理如下,
两阶段提交相关代码的分析由于过于复杂,有时间再用其它文章说明
- prepare阶段
![a7dbdb91077408b3556d57bd25d4e33c.png](https://i-blog.csdnimg.cn/blog_migrate/c78c50df653bef059c74664468a0734f.png)
- commit阶段
![da7e3990e65e10560821e129509cab03.png](https://i-blog.csdnimg.cn/blog_migrate/be429bda691c5be83e44c9b3fb18e0cb.jpeg)
很显然,区别主要两点点:
- after_sync模式下,commit队列会持有LOCK_commit互斥锁,下一队列一定会挂住
- after_sync模式下,commit队列中的线程会持有事务内的锁,凡是涉及这些锁竞争的其它队列中的事务一定会挂住
情景测试
MySQL版本:5.7.24
参数:sync_binlog = 1
参数:innodb_flush_log_at_trx_commit = 1
测试方式:40个线程并发,0.5s写入一次
分析方法:每种测试场景下,通过perf采样两分钟的数据,通过火焰图对比分析
命令参考:perf record -F 99 -p process_id -g -- sleep 30; perf script | ./stackcollapse-perf.pl > out.perf-folded; ./flamegraph.pl out.perf-folded > perf-kernel.svg
- 无热点数据
after_sync模式
![0f0a3d890ea2d41c48b756e007f52b41.png](https://i-blog.csdnimg.cn/blog_migrate/55e3f5d143fa1f6cc0a8cf17c31125fb.jpeg)
after_commit模式
![abd0ebbb000d73730bf69491965df4ce.png](https://i-blog.csdnimg.cn/blog_migrate/62551a05adbf9199940cfde4e85c072f.jpeg)
分析:
after_sync -> after_commit
Binlog_storage_delegate::after_flush: 0.17% -> 0.13% // 获取互斥锁LOCK_binlog_更频繁,即更频繁地锁住Binlog
MYSQL_BIN_LOG::change_stage: 0.88% -> 2.10% // 获取锁的时间延长
MYSQL_BIN_LOG::flush_cache_to_file: 0.44% -> 0.45% // 写binlog文件缓存请求稍微有点增长
MYSQL_BIN_LOG::process_commit_stage_queue: 0.44% -> 0.77% // 存储引擎层提交次数增多
MYSQL_BIN_LOG::process_flush_stage_queue: 1.61% -> 1.87% // Binlog线程缓存写文件缓存增多,重做日志刷盘增多
MYSQL_BIN_LOG::sync_binlog_file: 0.66% -> 0.72% // Binlog刷盘请求增多
MYSQL_BIN_LOG::process_after_commit_stage_queue: _.__% -> 1.52 // after_sync模式下,几乎没有after_commit相关逻辑的耗时
call_after_sync_hook: 0.10% -> 0.06% // after_commit模式下,after_sync相关逻辑的耗时没有完全消失
结论:after_sync模式下,由于组提交而更便于集中处理,从半同步插件的调用次数、组提交队列的流转次数、Binlog的请求写次数与刷盘次数,重做日志的刷盘次数等,都出现了减少。因此,after_sync性能是优于after_commit的。
- 有热点数据
after_sync模式
![218ddae837298efc27428911c569168c.png](https://i-blog.csdnimg.cn/blog_migrate/ddf1b8b1336f796aef3b4b514ae95d01.jpeg)
after_commit模式
![5d92ec69499573a0046861ce6a4d3d7b.png](https://i-blog.csdnimg.cn/blog_migrate/9f313bad77c4f6f776b0f77321d22ff9.jpeg)
分析:
MYSQL_BIN_LOG::ordered_commit: 2357 samples, 15.19% -> 3954 samples, 9.75%
// 提交请求次数更多
lock_trx_release_locks: 1634 samples, 10.53% -> 502 samples, 1.24%
// 从lock_rec_grant函数可以看到,获取记录锁的时间大大增加了
看图已经一目了然,在after_sync模式下,lock_trx_release_locks函数占比非常突出。大致估算下,从after_sync模式到after_commit模式,CPU时间由原来的10.53%/15.19%=69.3%降到了1.24%/9.75%=12.7%!
结论:有热点数据,不用废话,after_commit模式,选它,选它,就选它!