事务提交失败常因代码未捕获commit()异常,导致数据未持久化;锁超时需查长事务而非盲目重试;AUTOCOMMIT=0下须确保终态处理,否则连接回收时事务被强制回滚。

事务提交时 COMMIT 报错,程序没感知怎么办
MySQL 的 COMMIT 本身不会“静默失败”——它要么成功,要么抛出明确错误(比如连接断开、锁超时、XA 状态异常)。但很多业务代码只捕获 execute 阶段的异常,漏掉 commit() 这一步的异常处理,导致事务看似“执行了”,实际根本没提交。
常见现象:SQL 执行成功,日志显示“已更新”,但查库发现数据没变;或者程序重启后数据回滚,却没人知道哪步断了。
- 用 Python 的
mysql-connector-python或pymysql时,conn.commit()是独立调用,必须显式包裹try/except - Java 的 JDBC 同理:
connection.commit()可能抛SQLException,不能只 catchexecuteUpdate()的异常 - Go 的
sql.DB中,tx.Commit()返回error,不检查就等于放弃事务最终状态确认
遇到 Lock wait timeout exceeded 时别硬重试
这个错误不是网络抖动,是别的事务长期占着你要更新的行或间隙锁,重试 3 次大概率还失败。盲目重试反而加剧锁竞争,拖垮整个表的写入吞吐。
真实场景:秒杀扣库存、订单状态机更新、账务流水插入——都容易在高并发下触发。
- 先查
SELECT * FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(NOW()) - TIME_TO_SEC(TRX_STARTED) > 60,看有没有运行超 1 分钟的长事务 - 应用层应设合理超时,比如
SET innodb_lock_wait_timeout = 5(会话级),避免卡死 50 秒再报错 - 重试逻辑要带退避(如指数退避),且上限建议 ≤ 2 次;超过就记录告警,人工介入查锁源
AUTOCOMMIT=0 下忘记 commit 或 rollback 的后果
连接空闲时没显式结束事务,MySQL 不会自动帮你提交或回滚。连接被连接池回收后,事务状态直接丢失——InnoDB 会强制回滚,但你完全不知道发生了什么。
典型表现:本地测试正常,上生产后偶尔丢数据、偶现“幻读”、监控看到大量 TRX_STATE = RUNNING 的僵尸事务。
- 所有手动开启事务的路径,必须确保
finally/defer/__exit__中调用rollback()或commit() - 连接池配置里打开
reset_connection=True(PyMySQL)或useAffectedRows=true(MySQL JDBC)可缓解,但不能替代代码保障 - 上线前跑 SQL 检查:
SELECT COUNT(*) FROM information_schema.INNODB_TRX WHERE TRX_STATE = 'RUNNING' AND TRX_STARTED
主从延迟大时,COMMIT 成功 ≠ 从库已同步
MySQL 默认是异步复制,COMMIT 返回成功只代表 binlog 已刷盘、主库事务完成,从库可能还在追。如果你紧接着在从库查刚写的记录,大概率查不到。
这不是 bug,是架构取舍。强一致性场景(如支付结果页跳转后立刻查订单)必须绕过从库,或启用半同步(rpl_semi_sync_master_enabled=ON)。
- 不要在写完立即读从库的代码里加
SLEEP(1)—— 延迟不可控,且污染业务逻辑 - 如果必须读己写,优先走主库(加
/*FORCE_MASTER*/注释或路由标记),而不是等复制 - 半同步不是银弹:当从库响应慢,主库会卡在
commit直到超时(默认 10 秒),需调rpl_semi_sync_master_timeout并监控Rpl_semi_sync_master_no_times
事务安全真正的难点不在语法,而在边界:连接中断时 commit 是否发出、主从切换瞬间的 binlog 位置、连接池归还前事务残留……这些地方没有银弹,只有每一步都假设它会失败,并验证它确实失败了。


















