COMMIT本身极少失败,所谓“提交失败”95%是误判:主因是前置DML报错被忽略、连接假成功、autocommit状态误配或DDL隐式提交导致事务已终结。

MySQL 的 COMMIT 本身极少真正“失败”——它不校验数据、不检查约束、不等待刷盘完成,只要事务还活着、连接没断、权限正常,COMMIT 命令就会返回成功。所谓“提交失败”,95% 是误判:要么是前置 DML 报错被忽略,要么是连接假成功、事务已回滚,要么是应用没处理好 autocommit 状态。
为什么 COMMIT 返回成功但数据没生效
这是最隐蔽的问题,表面看没报错,实则数据丢失。根本原因不是 COMMIT 失效,而是它执行时已无有效事务可提交。
-
autocommit=1模式下手动执行了BEGIN,但后续忘记COMMIT,连接关闭时自动回滚 - 连接池返回了一个已失效的连接(如网络闪断后未重置状态),客户端发了
COMMIT,服务端早已回滚,却因 TCP ACK 送达让客户端误以为成功 - 事务中执行了
ALTER TABLE等 DDL,触发隐式提交,后续COMMIT实际在空事务上运行,无效果也不报错 - 应用层捕获了 DML 错误(如
ERROR 1062主键冲突)但没做ROLLBACK,残留的事务状态导致后续操作混乱
验证方式:执行 SELECT @@in_transaction;,值为 0 却还调 COMMIT,就属于无效提交。
捕获并重试 Deadlock found when trying to get lock(错误码 1213)
这不是故障,是 InnoDB 正常的死锁检测行为。MySQL 主动选一个事务回滚,你看到的报错就是那个被牺牲的事务。
- 必须在应用层捕获
ERROR 1213 (40001)或字符串匹配Deadlock found when trying to get lock,不能只 catch 通用异常 - 重试最多 3 次,每次前加随机延迟(如
time.sleep(random.uniform(0.01, 0.1))),避免重试风暴 - 检查多表更新顺序是否统一:所有涉及
users和orders的事务,都先UPDATE users再UPDATE orders - 确保
WHERE条件走索引,全表扫描会锁整张表,大幅提高死锁概率
别试图“完全避免死锁”,高并发下它必然存在;重点是让重试快、轻、稳。
Lock wait timeout exceeded(错误码 1205)怎么定位和收敛
这和死锁不同,是纯等待超时:一个事务卡太久,别的事务等不及了。问题不在报错那句 COMMIT,而在它前面某条语句早被阻塞住了。
- 立刻查
SELECT * FROM information_schema.INNODB_TRX\G,重点关注TRX_STATE = 'LOCK WAIT'和TRX_STARTED时间过早的事务 - 配合
SELECT * FROM information_schema.INNODB_LOCK_WAITS;找出谁在等谁、等什么锁 - 常见诱因:长事务未提交、
UPDATE没走索引锁全表、事务内做了 HTTP 调用或文件读写 - 临时缓解可调低
innodb_lock_wait_timeout到 10 秒,让问题更快暴露,而非延长等待
注意:SHOW ENGINE INNODB STATUS\G 的 LATEST DETECTED DEADLOCK 区域只记录死锁,不记录锁等待超时,别指望它。
存储过程或触发器导致 COMMIT 失败的典型表现
过程体里一旦出现事务控制语句,就会破坏外层事务一致性。MySQL 不允许嵌套事务,也不透明传递上下文。
- 过程里写了
START TRANSACTION、COMMIT或ROLLBACK,调用方再COMMIT会直接报ERROR 1305 (42000): SAVEPOINT does not exist或权限类假错 - 过程内有
INSERT/UPDATE但没声明DECLARE EXIT HANDLER FOR SQLEXCEPTION,异常发生后事务状态残留,外部COMMIT可能静默失败 - 正确做法:过程只做 DML,不碰事务边界;异常由上层用
SIGNAL SQLSTATE '45000'抛出,由调用方决定回滚 - 真需自治事务?仅限 MySQL ≥ 8.0.27 + 显式启用
innodb_autocommit配置,且必须用CREATE PROCEDURE ... MODIFIES SQL DATA显式声明
所有过程上线前,务必用 SELECT ROUTINE_DEFINITION FROM information_schema.ROUTINES 检查源码,确认没有 COMMIT 字样。


















