MySQL事务需显式开启,START TRANSACTION或BEGIN均可;自动提交默认开启,必须关闭才能多语句事务;COMMIT成功不保证落盘,依赖innodb_flush_log_at_trx_commit配置;错误不自动回滚,需应用层捕获并ROLLBACK;长事务影响MVCC清理与性能。

MySQL事务必须显式开启,BEGIN 和 START TRANSACTION 效果相同
MySQL默认是自动提交模式(autocommit=1),每条DML语句执行完立刻生效,根本不会进入事务。想用事务包裹多条DML,第一步就是关掉自动提交——但不是靠改全局配置,而是用语句显式开启事务块。
推荐用 START TRANSACTION,语义更清晰;BEGIN 是别名,部分老文档写法,两者无区别。注意:BEGIN 不是存储过程里的 BEGIN...END 块,别混淆。
-
SET autocommit = 0虽然也能进事务模式,但容易漏关、难追踪,不建议在应用逻辑中用 - 开启后,所有后续DML(
INSERT/UPDATE/DELETE)都暂存在当前事务中,直到COMMIT或ROLLBACK - 事务开启后,如果连接断开或客户端异常退出,MySQL会自动
ROLLBACK,这点比某些ORM更可靠
COMMIT 成功不代表数据已刷盘,innodb_flush_log_at_trx_commit 才决定持久性
执行 COMMIT 后,MySQL返回“Query OK”,只表示事务已提交到InnoDB日志缓冲区,是否真正落盘取决于配置项 innodb_flush_log_at_trx_commit 的值:
-
1(默认):每次COMMIT都触发fsync刷redo log到磁盘 → 强持久,性能略低 -
0:每秒刷一次日志,事务提交只写内存 → 可能丢一秒内事务 -
2:写入操作系统缓存即返回,由OS异步刷盘 → crash可能丢数据,但比0稍安全
线上业务强烈建议保持为 1。别只看 COMMIT 返回成功就认为万无一失。
事务中遇到错误不会自动回滚,必须由客户端主动判断并执行 ROLLBACK
MySQL本身不会因为某条DML报错(比如违反唯一约束、外键失败、字段超长)就中断整个事务或自动回滚。它只是让那条语句失败,事务状态仍为“活跃”,后续语句照常执行——这极易导致脏数据残留。
正确做法是:在应用层捕获SQL错误码(如 1062 重复键、1452 外键约束失败),一旦出错立即发 ROLLBACK。例如:
START TRANSACTION; INSERT INTO orders (id, user_id) VALUES (1001, 123); INSERT INTO order_items (order_id, product_id) VALUES (1001, 999); -- 假设order_id=1001不存在,触发1452错误 -- 此时事务未结束!必须手动ROLLBACK,否则下一条语句还在这个事务里
很多ORM(如Python的SQLAlchemy、Java的Spring JDBC)支持声明式事务回滚,但底层仍是靠检测异常后发 ROLLBACK 指令。
长事务会阻塞MVCC清理,SELECT 也可能被锁,别以为只DML才进事务
事务不只是包裹 INSERT/UPDATE/DELETE。只要开了事务,哪怕只执行一条 SELECT,也会持有事务ID,影响InnoDB的purge线程清理undo log。更关键的是:
- 带
FOR UPDATE或LOCK IN SHARE MODE的SELECT会加行锁,阻塞其他事务修改对应行 - 即使普通
SELECT,在READ COMMITTED或REPEATABLE READ隔离级别下,也会基于事务启动时刻的快照读取,拖慢全局版本链清理 - 一个运行30秒的事务,可能让undo log膨胀、history list堆积,最终拖慢整个实例
所以“事务包裹多条DML”这事,重点不在语法怎么写,而在于控制事务粒度:尽量短,别在事务里做HTTP调用、文件读写、循环sleep这类耗时操作。


















