MySQL 8.0 存储过程中BEGIN仅表示语句块起始,不开启事务;必须用START TRANSACTION显式开启,并配合DECLARE CONTINUE HANDLER FOR SQLEXCEPTION捕获异常,否则事务静默失效或悬挂。

MySQL 8.0 存储过程中必须显式使用 START TRANSACTION,不能依赖 BEGIN;回滚和提交必须由异常处理器驱动,否则事务会静默失效。
为什么存储过程里 BEGIN 不等于开启事务
MySQL 会把存储过程中的 BEGIN 自动识别为语句块起始符(即 BEGIN ... END),而不是事务命令。哪怕你写 BEGIN; UPDATE ...; COMMIT;,也只会执行 UPDATE 并自动提交——因为默认 autocommit = 1,且 BEGIN 没触发事务上下文。
唯一可靠的方式是用 START TRANSACTION 显式开启。它会临时关闭当前 session 的 autocommit,直到遇到 COMMIT 或 ROLLBACK 才恢复原状态。
-
START TRANSACTION和BEGIN在存储过程里完全不等价,后者不能替代前者 - 如果漏写
START TRANSACTION,所有 DML 都会立即生效,ROLLBACK无效 -
SET autocommit = 0虽然也能关自动提交,但不推荐:它影响整个 session,容易被后续语句意外覆盖
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION 是事务成败的关键
存储过程没有“try/catch”语法,必须靠声明异常处理器来捕获 SQL 错误(比如主键冲突、字段超长、外键约束失败)。不设 handler,一旦出错过程直接中断,事务既不提交也不回滚,留下悬挂状态。
典型写法是定义一个标志变量,出错时置位,再在结尾统一判断:
DECLARE t_error INTEGER DEFAULT 0; DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET t_error = 1;
- 必须在
START TRANSACTION之前声明 handler,否则事务内发生的异常无法被捕获 -
CONTINUE HANDLER是必须的;EXIT HANDLER会导致过程提前退出,跳过COMMIT/ROLLBACK判断逻辑 - 仅靠
IF t_error = 1 THEN ROLLBACK; ELSE COMMIT; END IF;不够——还要确保 handler 真正触发,比如故意插入违反唯一索引的数据来测试
哪些操作会让事务提前提交,导致 ROLLBACK 失效
MySQL 中某些语句具有隐式提交(implicit commit)行为,一旦出现在事务中,会立刻提交当前事务并结束它。此时再执行 ROLLBACK 已无意义,因为它面对的是一个已终止的事务。
常见隐式提交语句包括:CREATE TABLE、DROP TABLE、ALTER TABLE、TRUNCATE TABLE、LOCK TABLES、SET autocommit = 1、甚至另一个 START TRANSACTION。
- 在存储过程中混用 DDL(如建临时表)和 DML,极易踩坑;建议拆分为多个过程或改用
CREATE TEMPORARY TABLE(它不触发隐式提交) -
SELECT ... FOR UPDATE或LOCK IN SHARE MODE不会隐式提交,但要注意锁等待超时会抛出异常,需被 handler 捕获 - 事务中执行
SHOW、EXPLAIN等管理语句一般安全,但避免在事务块里调用FLUSH或RESET类命令
完整可运行的最小事务存储过程模板
以下是一个 MySQL 8.0 下验证通过的最小事务过程,含异常捕获、明确的提交/回滚分支、无隐式提交风险:
DELIMITER $$
CREATE PROCEDURE transfer_money(
IN from_id INT,
IN to_id INT,
IN amount DECIMAL(10,2)
)
BEGIN
DECLARE t_error INTEGER DEFAULT 0;
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET t_error = 1;
<pre class='brush:php;toolbar:false;'>START TRANSACTION;
UPDATE accounts SET balance = balance - amount WHERE id = from_id;
UPDATE accounts SET balance = balance + amount WHERE id = to_id;
IF t_error = 1 THEN
ROLLBACK;
ELSE
COMMIT;
END IF;END$$ DELIMITER ;
调用时注意:确保 accounts 表引擎为 InnoDB,且字段 id 存在有效值。若第二条 UPDATE 因外键或空值失败,t_error 被置为 1,第一条 UPDATE 会被回滚。
真正容易被忽略的是 handler 的作用域和隐式提交的边界——它们不出现在错误信息里,只在数据不一致时暴露。写完务必用非法输入(如不存在的 id)触发异常,观察余额是否还原。


















