应使用 start transaction 而非 begin 开启事务,因 start transaction 是 sql 标准语法,语义明确、兼容性好、支持扩展子句且不受 autocommit 干扰;begin 仅在存储程序中表示语句块,在普通会话中易引发误解和错误。

START TRANSACTION 是唯一安全的事务开启方式
用 BEGIN 或 BEGIN WORK 虽然语法合法,但容易和存储过程中的 BEGIN ... END 块混淆,尤其在嵌套逻辑里可能被误判为语句块起始而非事务控制。生产环境应统一使用 START TRANSACTION,它明确表示事务边界,且不会与任何语法结构冲突。
必须确认当前会话的 autocommit 状态:执行 SELECT @@autocommit;,返回 1 表示自动提交已启用——此时即使写了 START TRANSACTION,后续单条语句仍可能因隐式提交而失效(比如执行了 CREATE TABLE 这类 DDL 语句)。
- 若需长期手动控制,可临时设为
SET autocommit = 0;,但要注意连接复用时该设置会延续,容易导致意外长事务 - 更推荐做法:每次只在业务逻辑入口显式
START TRANSACTION,不改动全局autocommit - 执行
START TRANSACTION后,立刻查SELECT TRX_ID FROM INFORMATION_SCHEMA.INNODB_TRX WHERE TRX_MYSQL_THREAD_ID = CONNECTION_ID();可验证事务是否真正激活
ROLLBACK 不会自动触发,必须由你显式调用
MySQL 从不因为某条 SQL 报错就自动回滚整个事务。例如插入主键冲突(错误码 1062),InnoDB 默认只回滚那一条 INSERT,前面成功的 UPDATE 依然有效——这和多数开发者的直觉相反。
常见错误现象:START TRANSACTION; UPDATE t1 SET x=1; INSERT INTO t2 VALUES (1); -- 报 1062; SELECT * FROM t1; 结果发现 t1 的更新还在,事务没整体回滚。
- 应用层必须捕获异常(如 Python 的
except Exception、Java 的catch (SQLException e)),并在其中调用rollback() - 存储过程中要用
DECLARE EXIT HANDLER FOR SQLSTATE 'HY000'或具体错误码(如1062),然后在 handler 里设标志位并跳转到ROLLBACK分支 - 不要依赖“出错后连接断开就自动回滚”——如果事务未提交且连接异常中断,MySQL 确实会回滚,但这属于兜底机制,不能作为设计依据
SAVEPOINT 支持局部回滚,但不能替代事务整体控制
当一组操作中只有最后几步可能失败,又不想丢弃前面已完成的合理变更时,SAVEPOINT 才有意义。比如先写日志表(必须成功),再更新业务表(可能失败),就可以在日志写完后设 SAVEPOINT sp1,出错时 ROLLBACK TO sp1。
但它有硬限制:无法跨存储引擎生效;不能在触发器或函数内部创建;一旦执行了会隐式提交的语句(如 ALTER TABLE、CREATE INDEX),所有保存点立即失效。
- 创建保存点:
SAVEPOINT sp_name; - 回滚到保存点:
ROLLBACK TO sp_name; - 删除保存点(不回滚):
RELEASE SAVEPOINT sp_name; - 注意:
ROLLBACK TO后事务仍处于活跃状态,仍需最终COMMIT或ROLLBACK
隐式提交是事务失效最隐蔽的元凶
很多看似“没动过表”的语句会偷偷提交当前事务,比如 ANALYZE TABLE、OPTIMIZE TABLE、RENAME TABLE、甚至某些 SET 语句(如修改 sql_log_bin)。一旦执行,前面所有未提交的变更立刻落地,ROLLBACK 就无效了。
最容易踩的坑:在事务中调用 SELECT SLEEP(1) 测试并发,结果发现事务莫名提交了——其实不是 SLEEP 导致的,而是你在它之前或之后不小心执行了隐式提交语句,只是没注意到。
- 完整隐式提交语句列表见 MySQL 官方文档 “Statements That Cause an Implicit Commit”
- 开发时可在事务块开头加注释
-- DO NOT INSERT DDL OR ADMIN COMMANDS HERE提醒自己 - 用
SHOW ENGINE INNODB STATUS\G查看TRANSACTIONS部分,确认事务 ID 是否在执行可疑语句后发生变化
事务真正可靠的前提,是你清楚每一行 SQL 对事务状态的影响,而不是依赖“好像没报错就默认成功”。隐式提交、语句级回滚、存储引擎限制——这些细节漏掉任何一个,都可能导致数据不一致。











