怎么爬before after之间的内容_MySQL的after_sync与after_commit性能之争源码剖析与情景测试...

3c02bac8bdc68dd7d8e9463bc69c2dcc.png

背景

在已知环境和认知下,更新热点数据时,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
  • commit阶段

da7e3990e65e10560821e129509cab03.png

很显然,区别主要两点点:

  1. after_sync模式下,commit队列会持有LOCK_commit互斥锁,下一队列一定会挂住
  2. 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
after_commit模式

abd0ebbb000d73730bf69491965df4ce.png

分析:

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
after_commit模式

5d92ec69499573a0046861ce6a4d3d7b.png

分析:

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模式,选它,选它,就选它!

<think>嗯,用户问的是@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)的意思。首先,我需要回忆一下Spring框架中的事件处理机制。记得Spring有@EventListener注解,用来处理应用事件。而TransactionalEventListener是它的扩展,专门和事务关联的。 那这里的phase参数设置为AFTER_COMMIT,应该是在事务提交之后触发事件监听器。但具体细节可能需要注意。比如,默认情况下,TransactionalEventListener的phase是什么?可能用户想知道为什么需要特别指定AFTER_COMMIT。 然后要区分AFTER_COMMIT和其他阶段的不同,比如BEFORE_COMMITAFTER_ROLLBACK、AFTER_COMPLETION。需要解释清楚每个阶段的作用,特别是AFTER_COMMIT的应用场景。比如,当需要在事务成功提交后才执行某些操作,比如发送通知或消息,避免事务未提交导致数据不一致。 另外,使用AFTER_COMMIT时,事件监听器中的代码会在事务提交后执行,这时候如果在这里进行数据库操作,会不会开启新的事务?需要说明这个时候的事务上下文情况,可能是在新的事务中,或者没有事务,取决于配置。 还要考虑使用场景的例子,比如用户注册后发送邮件,只有在用户数据成功提交后才触发发送,避免邮件发送了但事务回滚的情况。这能提高系统的可靠性。 可能用户在使用时遇到的问题,比如事件监听器没有执行,是不是因为主事务没有成功提交?或者需要配置事务管理器?需要提示这些注意事项,确保用户正确使用该注解。 总结一下,回答的结构应该是先解释注解的作用,再说明phase参数的不同值,重点放在AFTER_COMMIT上,举例说明使用场景,最后提到注意事项和常见问题。这样用户能全面理解这个注解的用途和如何正确应用。</think>`@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)` 是 Spring Framework 中用于处理事务性事件的注解。它的核心含义是:**当事务成功提交(COMMIT)后,触发事件监听器的执行**。以下是逐步解析: --- ### 1. 注解的作用 - **事务绑定事件**:将事件监听器的执行 Spring 事务的生命周期绑定。 - **阶段控制**:通过 `phase` 参数指定事件触发的具体时机(这里是事务提交后)。 --- ### 2. 核心参数 `phase` `TransactionPhase` 定义了事务事件的触发阶段: - `BEFORE_COMMIT`:事务提交前触发。 - `AFTER_COMMIT`(默认):事务成功提交后触发。 - `AFTER_ROLLBACK`:事务回滚后触发。 - `AFTER_COMPLETION`:事务完成(无论提交或回滚)后触发。 **`AFTER_COMMIT` 的含义**: - 只有当事务**成功提交**时,才会触发事件监听器。 - 如果事务回滚或未完成,监听器不会执行。 --- ### 3. 典型使用场景 ```java @Component public class MyEventListener { @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) public void handleEvent(MyEvent event) { // 在事务提交后执行的逻辑 // 例如:发送通知、清理缓存、调用外部系统等 } } ``` - **适用场景**:需要在事务提交后执行的操作(如发送邮件、记录日志、调用外部 API)。 - **避免的问题**:防止事务未提交时执行外部操作(比如发送了邮件但数据库回滚,导致数据不一致)。 --- ### 4. 关键注意事项 1. **事务传播行为**: - 监听器中的代码默认在**独立的事务**中执行(若无事务,则新建事务)。 - 可通过 `@Transactional(propagation = Propagation.NOT_SUPPORTED)` 关闭事务。 2. **事件发布条件**: - 必须在事务上下文中发布事件(例如通过 `ApplicationEventPublisher` 发布)。 - 如果代码没有事务,事件监听器可能不会触发。 3. **异步执行**: - 若需要异步处理,需配合 `@Async` 注解使用: ```java @Async @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) public void handleEventAsync(MyEvent event) { /* ... */ } ``` --- ### 5. 对比普通 `@EventListener` | 特性 | `@EventListener` | `@TransactionalEventListener` | |---------------------|------------------------|--------------------------------------| | 事务绑定 | 无 | 绑定事务阶段(如提交后) | | 触发时机 | 事件发布后立即执行 | 根据事务阶段决定是否执行 | | 适用场景 | 通用事件处理 | 需要事务保障的关键操作 | --- ### 总结 - **作用**:在事务提交后执行某些操作,确保操作事务结果一致。 - **典型场景**:发送通知、调用外部系统、记录审计日志等。 - **注意点**:事务传播行为、事件发布条件、异步处理需求。 合理使用此注解,可以显著提升事务性操作的可靠性! 🚀
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值