如何在MySQL中为当前会话锁定整表实现事务全局互斥?
MySQL全局事务串行互斥实现方案
你需要的是全实例范围内的事务串行能力——不管请求来自哪个会话,同一时间只允许一个事务运行,其余事务必须等前一个执行完成才能启动,直接用下面的方案即可,不需要调整现有业务表。
推荐方案:用MySQL内置命名锁实现
MySQL自带的用户级命名锁GET_LOCK()是最适配这个场景的方案,锁是实例级全局生效的,不绑定具体的表或行,生命周期可以完全和事务对齐,不会出现表锁的各种副作用。
固定执行流程
所有需要串行的事务,严格按以下顺序写逻辑:
- 开事务前先抢全局锁,设置合理的等待超时(单位秒,按你业务最长事务耗时设置,比如设300秒)
-- 锁名可以自定义,所有事务用同一个名字即可 SELECT GET_LOCK('global_tx_run_lock', 300);
返回值判断规则:
- 返回
1:抢锁成功,继续往下走 - 返回
0:等了300秒还没拿到锁,直接终止流程返回繁忙错误 - 返回
NULL:锁获取异常,直接终止流程
- 抢锁成功后正常开事务、跑业务逻辑
START TRANSACTION; -- 这里写你所有的业务SQL,不管是整表操作还是行操作都可以
- 不管业务执行成功还是失败,提交/回滚事务后必须立刻释放锁
-- 业务成功走提交 COMMIT; -- 业务失败走回滚 -- ROLLBACK; -- 释放全局锁,给下一个事务让路 SELECT RELEASE_LOCK('global_tx_run_lock');
方案特性说明
- 跨会话互斥:命名锁是全局的,只要有一个会话持有这把锁,其余所有会话抢同名锁都会被阻塞,直到锁被释放,完全满足跨会话不互相干扰的要求。
- 同会话兜底:正常情况下单个数据库连接(会话)同一时间只能处理一个请求,本身不可能并行跑多个事务;如果是应用层连接管理有bug(比如多线程共用同一个连接没加同步)导致同会话出现并发事务请求,只要所有逻辑都严格先抢锁,后发起的请求会因为前一个请求没释放锁被阻塞,不会出现逻辑穿插。
- 无业务侵入:不需要锁业务表,不会影响业务SQL的执行效率,也不会出现表锁导致的“锁了A表就不能操作B表”的问题。
备选方案:独立锁表+事务级行锁
如果是极老版本MySQL不支持命名锁,可以建一张专门的锁表,用InnoDB的行排他锁实现:
- 建一张只有一行记录的专用锁表,不存业务数据
CREATE TABLE `tx_global_lock` ( `id` tinyint NOT NULL DEFAULT 1, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 初始化唯一一条锁记录 INSERT INTO `tx_global_lock` VALUES (1);
- 事务执行流程
START TRANSACTION; -- 事务第一步先锁这行记录,锁会持续到事务结束 SELECT 1 FROM `tx_global_lock` WHERE id = 1 FOR UPDATE; -- 执行所有业务SQL COMMIT; -- 事务提交/回滚时会自动释放行锁,不需要手动解锁
这个方案的跨会话互斥能力和命名锁一致,InnoDB的行排他锁会一直持有到事务结束,前一个事务没提交,其余所有事务的FOR UPDATE语句都会被阻塞。唯一的区别是行锁对同会话是重入的,如果真的出现同连接并发的极端情况,不会阻塞本会话请求,需要在应用层给连接加同步兜底。
避坑提醒
- 不要用
LOCK TABLES实现这个需求:LOCK TABLES是会话级锁,事务提交回滚不会自动释放,同会话重复加锁还会自动释放之前的锁,很容易出现锁逻辑混乱,而且加锁后无法操作其他未声明的表,灵活性极差。 - 所有需要串行的事务必须严格遵守「先抢锁再执行业务」的流程,漏了抢锁步骤就会绕过互斥逻辑。
- 锁等待超时不要设太短,必须覆盖你业务最长的事务执行时间,避免正常运行的长事务被误判超时。
内容的提问来源于stack exchange,提问作者Kai
相关产品推荐
相关产品推荐

