mysql存储过程内禁止使用start transaction,因其事务必须由客户端会话控制;过程内dml默认自动提交,autocommit=0无效;正确做法是调用前显式开启事务,过程仅执行dml并抛异常。

不能在MySQL存储过程中正确使用 START TRANSACTION —— 它根本就不是设计用来在过程体内执行的。
为什么存储过程里写 START TRANSACTION 会出错或失效
MySQL 在运行存储过程时,会将过程体视为一个“原子执行单元”,但事务边界必须由客户端会话控制。一旦你在过程里写 START TRANSACTION,会出现以下情况之一:
- 运行时报错:
ERROR 1290 (HY000): The MySQL server is running with the --disable-gtid-strict-mode option(实际与 GTID 无关,是内核禁止) - 静默提交当前活跃事务,导致外层事务意外中断
- 嵌套调用时,内部
START TRANSACTION把上层未提交的修改直接落盘 - 即使没报错,
COMMIT也会提交整个会话事务,可能连带其他无关 DML
autocommit = 0 也不能让存储过程自动进入事务
很多人以为只要先执行 SET autocommit = 0,再调用含 DML 的存储过程,就能回滚——这是错的。MySQL 在进入存储过程时会临时把 autocommit 强制设为 1,过程结束才恢复原值。所以过程内的 UPDATE 默认自动提交,不受外层控制。
- 过程内所有 DML 都立即生效,无法靠外层
ROLLBACK撤销 - DDL 语句(如
CREATE TEMPORARY TABLE)更危险:它会隐式触发COMMIT,连带提交前面所有未提交的修改 - 唯一可靠方式是:调用前显式执行
START TRANSACTION,再调用过程,最后由调用方决定COMMIT或ROLLBACK
怎么写一个真正能参与事务的转账存储过程
过程只做两件事:执行 DML + 抛异常;事务控制全交给调用端。示例如下:
DELIMITER $$
CREATE PROCEDURE TransferMoney(
IN from_id INT,
IN to_id INT,
IN amount DECIMAL(10,2)
)
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
RESIGNAL; -- 不自己 ROLLBACK,把错误抛给调用方
END;
<p>UPDATE accounts SET balance = balance - amount WHERE id = from_id;
UPDATE accounts SET balance = balance + amount WHERE id = to_id;</p><p>-- 可选:检查影响行数,防止无匹配账户
IF ROW_COUNT() != 2 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Transfer failed: account not found';
END IF;
END$$
DELIMITER ;</p>
- 过程内不出现
START TRANSACTION、COMMIT、ROLLBACK - 用
DECLARE EXIT HANDLER捕获异常并RESIGNAL,确保调用方能感知失败 - 调用时必须包裹在事务中:
START TRANSACTION; CALL TransferMoney(1,2,100.00); COMMIT; - 如果过程被多个地方复用(比如搭配日志记录),统一由上层脚本或应用控制事务边界
最容易被忽略的一点:哪怕你把过程写得再“完美”,只要调用端没开事务,或者中间混入了 DDL 或 SET autocommit,整个转账逻辑就失去原子性。事务不是藏在过程里的开关,而是连接会话的上下文状态。











