开发者
资源
MySQL 高并发写卡在 Trx-sys 锁上:无锁哈希改造实录
MySQL 高并发写卡在 Trx-sys 锁上:无锁哈希改造实录
原创
发表于09/18
150

OLTP 场景写一多,瓶颈往往不在 SQL 本身,而在事务管理器的锁上。所有并发的 Insert、Update、Delete 都要碰 Trx-sys 这个全局实例,它拿链表、数组、std::set 这些容器维护事务记录,外面罩一把 Trx-sys->mutex。高并发写的时候,这把锁的竞争直接变成吞吐量天花板。

我先用 profiling 确认了热点,发现临界区里最耗时的两个操作是 rw_trx_set.insert 和 rw_trx_set.erase。rw_trx_set 是 std::set,底层是平衡树,插入删除都要 O(logn) 旋转。源码注释里原作者也试过 std::unordered_set,我们测试下来并没有性能优势。绕开树,直接上无锁哈希。

思路说起来简单:用无锁哈希对象 rw_trx_hash 替代 rw_trx_set。MySQL 自己在 performance schema 和 MDL 里就有一份 LF_HASH 实现,不用新造轮子,直接复用。新增两个类,rw_trx_hash_t 封装 LF_HASH,rw_trx_hash_element_t 是它的元素。这里有个容易翻车的地方:rw_trx_hash 的访问本身线程安全,但相关操作已经从 mutex 临界区挪出去了,它和临界区内的数据对象不再同步,逻辑正确性得自己保证。比如 trx->state = TRX_STATE_COMMITTED_IN_MEMORY 被移出临界区后,lock_rec_convert_impl_to_expl 可能看到事务已被移出 rw_trx_hash 但状态还没改完,正确性要靠它最终走 lock_rec_convert_impl_to_expl_for_trx 再确认一次状态来兜底。文档里这段注释我看了好几遍才理清。

改动还删掉了一批接口:trx_rw_is_active、trx_get_rw_trx_by_id、trx_sys_rw_trx_add 这些语义有问题的直接删除,统一换成 rw_trx_hash.find 和 insert。

使用上有两个前置条件,别跳步。第一,这个特性基于 MySQL 8.0.20 开发,且前置特性是细粒度锁优化,必须先把 0001-SHARDED-LOCK-SYS.patch 合进去,再合 0002-LOCK-FREE-TRX-SYS.patch,顺序反了会报错。合补丁前记得 dos2unix 转格式,git apply --check 先验一下:

dos2unix 0001-SHARDED-LOCK-SYS.patch
git apply --check 0001-SHARDED-LOCK-SYS.patch
git apply --whitespace=nowarn 0001-SHARDED-LOCK-SYS.patch
dos2unix 0002-LOCK-FREE-TRX-SYS.patch
git apply --check 0002-LOCK-FREE-TRX-SYS.patch
git apply --whitespace=nowarn 0002-LOCK-FREE-TRX-SYS.patch

第二,判断该不该上这个特性有个信号:已经应用细粒度锁优化后,用 Performance Schema 还能观测到 trx_sys_mutex 热点,而且 CPU 利用率不高,说明线程都在等锁,这时候上无锁优化正合适。补丁合入重新编译就生效,不需要配系统变量。

按官方文档的说法,这个特性能让 Sysbench 写场景性能提升 20%(数据来自官方文档)。我验证过的场景是 update 密集型的读写混合,提升确实明显。你们在生产上触发过 trx_sys_mutex 热点吗?怎么判断值不值得上这个补丁的?

参考:鲲鹏社区官方文档《MySQL无锁优化 特性指南》(https://www.hikunpeng.com/document/detail/zh/boostdb/mysql/lock_atomic_var_opt/docs/zh/mysql_lock_free_tuning_feature_guide.md)

收藏举报
Level 1
0
帖子
0
粉丝
0
获赞