MySQL事务是InnoDB引擎支持的DML操作逻辑集合,必须全部成功或全部失败;默认autocommit=1导致转账可能数据不一致;需显式使用START TRANSACTION和COMMIT保障原子性。

MySQL事务 是一组 DML 操作(INSERT、UPDATE、DELETE)的逻辑集合,它必须全部成功,或全部失败——没有“一半生效”的中间状态。这是 InnoDB 存储引擎提供的核心能力,MyISAM 等引擎不支持事务,直接绕过这个机制。
为什么默认转账会出错?因为 autocommit=1
你写两条 UPDATE 转账语句,没加任何事务控制,结果张三扣了钱、李四没到账——这不是 bug,是 MySQL 的默认行为在起作用:
-
autocommit默认为1,即每条 DML 语句单独成一个事务,自动提交 - 第一条
UPDATE执行完立刻落盘;第二条若因网络中断、程序崩溃、SQL 错误而失败,已提交的第一条无法撤回 - 这就是典型的“数据不一致”:总金额凭空减少
✅ 正确做法:显式关闭自动提交,用 START TRANSACTION 包裹操作:
SET autocommit = 0; START TRANSACTION; UPDATE account SET money = money - 100 WHERE name = '张三'; UPDATE account SET money = money + 100 WHERE name = '李四'; COMMIT;
⚠️ 注意:SET autocommit = 0 只对当前会话有效;生产环境更推荐在代码里用 BEGIN/COMMIT 显式控制,避免依赖会话级配置。
事务不是万能锁:ACID 里最常被误解的是“一致性”
很多人以为“事务保证数据正确”,其实 consistency 不是数据库自动校验业务规则,而是指:事务前后,数据库必须满足你定义的约束(如主键、外键、CHECK、唯一索引等)。
- 如果你没给
money字段加CHECK(money >= 0),事务照样允许余额变成 -500 —— 这不算违反 ACID,只是你的业务一致性没落地 - 转账中 A 减、B 增,但没加外键或触发器校验双方账户存在?事务提交了,但业务上已经错了
-
COMMIT成功 ≠ 业务正确;它只代表“数据库层面没违反约束”
? 真正兜底要靠:约束定义 + 应用层校验 + 幂等设计(比如用转账单号防重)
事务隔离级别不是调高就安全:REPEATABLE READ 也挡不住幻读
MySQL 默认隔离级别是 REPEATABLE READ,它能防止脏读、不可重复读,但**无法完全避免幻读**(比如 SELECT ... FOR UPDATE 范围内新插入一行,再次查出来多了一条)。
-
READ COMMITTED:每次SELECT都读最新已提交版本 → 可能不可重复读,但幻读概率更低 -
REPEATABLE READ:基于 MVCC 快照读,可重复读,但快照不覆盖新插入行 → 幻读仍可能发生 -
SERIALIZABLE:加范围锁强制串行,性能差,一般不用
? 实际选择建议:
- 普通 Web 应用:保持默认
REPEATABLE READ,幻读场景手动加SELECT ... FOR UPDATE或用唯一业务键替代范围查询 - 强一致性金融场景:别只调隔离级别,配合乐观锁(
version字段)或分布式事务框架(如 Seata)
事务生命周期结束的 4 种方式,第 3 种最容易被忽略
事务不是开了就一直挂着,它会在以下任一情况自动终止:
-
COMMIT或ROLLBACK显式结束 - 执行了
DDL(如ALTER TABLE)、DCL(如GRANT)——这些语句会隐式提交当前事务 - 客户端连接异常断开(如超时、kill connection) → MySQL 会自动回滚未提交事务
- 用户会话正常退出(如命令行
exit)→ 同样自动回滚
⚠️ 关键陷阱:应用层长事务(比如导出报表+更新状态)若没设好连接超时,可能卡住锁、拖垮并发。务必在代码里明确 COMMIT 或 ROLLBACK,不要依赖自动清理。










