mysql事务安全需显式控制、隔离级别匹配业务且错误路径必须回滚;默认autocommit=1使单条dml自动提交,未形成事务体;显式开启事务须确认autocommit状态,推荐start transaction;commit/rollback须覆盖所有分支;隔离级别应按需选择,read committed适合多数oltp场景;长事务需监控,避免锁等待和数据不一致。

MySQL 中事务不是“开了就安全”,关键在于显式控制 + 隔离级别匹配业务场景 + 错误路径必须回滚。默认 autocommit=1 下,每条 INSERT/UPDATE/DELETE 都自动提交,根本没形成事务体。
显式开启事务并确认 autocommit 状态
很多问题根源是误以为执行了 BEGIN 就进了事务,其实如果连接层或会话里 autocommit 是 1,BEGIN 后的语句仍可能被立即提交。
- 先查当前设置:
SELECT @@autocommit;—— 返回1表示自动提交已开 - 临时关闭(会话级):
SET autocommit = 0;,之后所有 DML 都需手动COMMIT或ROLLBACK - 更推荐方式:
START TRANSACTION;(或BEGIN),它会隐式将autocommit设为 0,且在COMMIT/ROLLBACK后自动恢复原值 - 注意:应用框架(如 Spring 的
@Transactional)可能已接管事务控制,直接在 SQL 层开事务反而冲突
COMMIT 和 ROLLBACK 必须覆盖所有分支
写事务逻辑时,只在“成功路径”写 COMMIT 是常见错误。一旦中间语句报错(比如外键约束失败、唯一索引冲突、超时),后续语句不执行,但前面已执行的 DML 仍处于未提交状态——此时连接若断开或被复用,数据就卡在中间态。
- MySQL 不会自动回滚异常中断的事务,必须由客户端显式发
ROLLBACK - 在存储过程里,要用
DECLARE EXIT HANDLER FOR SQLEXCEPTION捕获错误并执行ROLLBACK - 应用代码中(如 Python/Java),
try...except块里 catch 到数据库异常后,必须调用connection.rollback(),不能只打印日志 - 避免在事务中调用可能长时间阻塞的操作(如 HTTP 请求、文件读写),否则事务空占锁,拖垮并发
隔离级别要按需选,别迷信 REPEATABLE READ
MySQL 默认是 REPEATABLE READ,但它不能完全避免幻读(InnoDB 用间隙锁缓解,但非绝对),而且在高并发更新场景下容易引发死锁;而 READ COMMITTED 虽然有不可重复读风险,却大幅降低锁竞争。
- 查当前级别:
SELECT @@transaction_isolation; - 设为读已提交(推荐多数 OLTP 场景):
SET TRANSACTION ISOLATION LEVEL READ COMMITTED; - 电商库存扣减类操作,若需防止超卖,光靠隔离级别不够,必须加
SELECT ... FOR UPDATE显式加行锁 -
SERIALIZABLE几乎等于单线程,仅适合极低并发、强一致要求的批处理,生产环境慎用
长事务是隐形杀手,监控比优化更紧迫
一个运行 30 秒的事务,可能让 information_schema.INNODB_TRX 里堆积几十个等待锁的查询,而你从错误日志里只看到 “Lock wait timeout exceeded”。
- 定期查长事务:
SELECT * FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(NOW() - trx_started) > 60; - 避免在事务内做非数据库操作(如循环发消息、调第三方 API)
- Web 请求中开启事务后,务必保证响应返回前已
COMMIT或ROLLBACK,不要依赖连接池自动关闭来“清理” - 注意:
SHOW PROCESSLIST只显示连接状态,看不出是否在事务中;真正要看INNODB_TRX表
事务真正的难点不在语法,而在“控制权移交”——当一条 SQL 报错,谁负责回滚?当连接被复用,上一个事务是否干净结束?这些边界情况,恰恰是线上数据不一致最常发生的角落。











