跨库事务本质不存在,所谓“悬挂锁”是微服务中单库事务失控所致;mysql不支持跨库/跨实例原子事务,所谓跨库事务实为应用层协调的多个本地事务,悬挂锁源于某服务事务未及时提交导致锁持有过久。

跨库事务根本不存在,所谓“悬挂锁”是单库事务在微服务链路中失控的表现
MySQL 本身不支持跨库(更不用说跨实例)的原子事务。你在微服务里看到的“跨库事务”,其实是多个独立数据库连接上各自开启的本地事务,靠应用层协调(比如 TCC、Saga 或两阶段提交模拟)。这时候所谓的“悬挂锁”,本质是某个微服务节点上的 MySQL 事务迟迟未提交或回滚,锁住行/间隙,而其他服务还在等它——不是 MySQL 在挂,是你的服务调用链卡住了。
排查 INFORMATION_SCHEMA.INNODB_TRX 中长时间运行的事务
真正造成阻塞的是那些没及时结束的本地事务。先确认是不是它们在拖后腿:
- 执行
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX WHERE TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) > 30;找出运行超 30 秒的事务 - 重点关注
trx_state = 'RUNNING'但trx_operation_state是'starting index read'或'fetching rows'的记录——说明 SQL 卡在执行中,不是单纯没提交 - 结合
trx_mysql_thread_id查SHOW PROCESSLIST,看对应线程是否处于Sleep状态却持有锁(典型是应用获取连接后忘了 commit/rollback)
禁止在事务内发起远程调用,尤其是 HTTP 或 RPC
这是微服务下死锁和悬挂锁最常见源头。一个事务开启后去调另一个服务,对方响应慢、超时、重试,本端事务就一直开着,锁一直挂着。
- 把所有外部依赖(发消息、调下游 API、写缓存、打日志)全部移到
COMMIT之后 - 如果必须强一致性,改用 Saga 模式:本地事务提交后,通过异步事件驱动下游,失败则发补偿事务
- 在 DAO 层加硬性检查:如 MyBatis 拦截器或 Spring AOP 切面,一旦检测到当前线程存在活跃事务且即将发起
RestTemplate或FeignClient调用,直接抛异常中断
用 innodb_lock_wait_timeout 快速暴露问题,别让锁无限等下去
默认 50 秒太长,微服务调用链通常 1–3 秒就要响应。设短一点,让失败更快浮出水面:
- 在配置中显式设置
innodb_lock_wait_timeout = 5(单位秒),而不是依赖全局默认值 - 配合应用层捕获
ER_LOCK_WAIT_TIMEOUT(错误码 1205)和ER_LOCK_DEADLOCK(1213),统一做有限重试(最多 1–2 次) - 注意:这个参数只影响锁等待超时,不影响事务本身的执行时间;它不能防止悬挂,但能让悬挂更快被发现和终止
真正的难点不在数据库配置,而在于服务边界是否清晰——当一个事务横跨三个服务、四个数据库、两种消息中间件时,“悬挂”不是偶然,是设计必然。锁不会自己挂起来,是代码把它忘在那儿了。











