MySQL默认autocommit=1,每条DML独立提交,无法回滚已执行部分;必须用START TRANSACTION显式开启事务,配合COMMIT/ROLLBACK,并避免DDL隐式提交。

MySQL 默认不保证多条 DML 的原子性,必须显式控制事务边界,否则每条语句单独提交,失败后无法回滚已执行的部分。
为什么 autocommit=1 时事务“没用”
MySQL 默认开启 autocommit=1,这意味着每个 INSERT、UPDATE、DELETE 都是独立事务。哪怕你写了两条 UPDATE,第一条成功、第二条报错,第一条也已经永久写入,无法自动撤回。
- 查看当前会话是否自动提交:
SELECT @@autocommit;(返回 1 表示开启) - 关闭它才能让后续语句归入同一事务:
SET autocommit = 0; - 注意:
SET autocommit = 0只影响当前连接,新连接仍按全局默认值(通常是 1)启动
正确开启和结束事务的三步法
手动事务必须明确界定起点、终点和失败处理路径,不能只靠 COMMIT。
- 起点:用
START TRANSACTION或BEGIN(二者等价,推荐前者,语义更清晰) - 中间:执行一组逻辑相关的
INSERT/UPDATE/DELETE,期间任何语句报错都不会自动回滚 - 终点:成功则
COMMIT;失败或需放弃则ROLLBACK—— 必须由你显式调用,没有“自动失败回滚”机制
示例:
START TRANSACTION; UPDATE accounts SET balance = balance - 100 WHERE id = 1; UPDATE accounts SET balance = balance + 100 WHERE id = 2; -- 若此处发生错误(如违反外键、唯一约束),必须手动 ROLLBACK COMMIT;
DDL 语句会偷偷提交当前事务
在事务中执行 CREATE TABLE、DROP INDEX、ALTER TABLE 等 DDL 语句,MySQL 会**隐式执行 COMMIT**,导致前面的 DML 立即生效且不可回滚。
- 现象:
START TRANSACTION; UPDATE ...; CREATE INDEX ...; ROLLBACK;→UPDATE依然生效 - 原因:InnoDB 对 DDL 实现了原子性 DDL,但它是以“事务外操作”方式完成的,会强制结束当前事务
- 对策:避免在业务事务中混用 DDL;如必须建索引,放在事务外单独执行
原子性真正靠 undo log 回滚,不是靠语法“看起来像一个块”
事务的原子性不是由 BEGIN/COMMIT 语法糖保证的,而是 InnoDB 在执行每条 DML 时,同步生成 undo log 记录旧值,并通过 roll_pointer 链接版本链。一旦调用 ROLLBACK,引擎就按这条链逐条恢复。
- 这意味着:即使事务中途崩溃,重启后 InnoDB 也能根据 undo log 完成未完成的回滚
- 但前提是事务还没
COMMIT—— 已提交的变更写入redo log并刷盘,就不可逆了 - 所以关键不在“写了几行 SQL”,而在“是否在同一个未提交的事务上下文中生成了可追溯的 undo 记录”
最容易被忽略的一点:事务边界必须由开发者严格维护,MySQL 不会替你猜哪几条语句该一起提交。漏写 START TRANSACTION 或忘记 ROLLBACK,原子性就形同虚设。


















