MySQL存储过程不管理事务边界,必须由应用层显式用START TRANSACTION开启、COMMIT/ROLLBACK结束;过程内设autocommit=0不可靠,嵌套START TRANSACTION会触发隐式提交,且仅InnoDB支持事务。

MySQL存储过程本身不管理连接生命周期,事务一致性必须靠显式控制 + 连接层配合,否则极易因隐式提交或连接复用导致数据错乱。
存储过程中不能依赖 autocommit=0 的持久设置
MySQL会话级的 autocommit 设置在存储过程退出后不保留,且很多连接池(如PHP PDO、Node.js mysql2)默认启用自动提交。即使你在过程开头写了 SET autocommit = 0,只要过程执行完、连接被放回池中,下次复用时很可能已是 autocommit = 1 状态。
- 不要在存储过程里用
SET autocommit = 0作为事务起点——它不可靠,也不符合连接复用场景 - 真正有效的起点只能是显式的
START TRANSACTION或BEGIN - 如果过程被嵌套调用(比如 A 调 B),而 B 里又写了
START TRANSACTION,会触发隐式提交——MySQL 不支持真嵌套事务,上层事务会被强制提交
长连接下必须由应用层主导事务边界
存储过程只是逻辑封装,不是事务容器。事务的 BEGIN、COMMIT、ROLLBACK 必须由调用方(应用代码)显式控制,否则无法应对连接复用、超时重连、连接中断等现实问题。
- PHP PDO 示例:必须在
$pdo->beginTransaction()后再调用存储过程,而不是让过程自己START TRANSACTION - Node.js mysql2 示例:用
connection.beginTransaction()开启,再connection.query('CALL transfermoney(?, ?, ?)', [...]),最后统一commit()或rollback() - AIOMySQL 异步场景下,
async with conn.begin()是唯一安全入口,过程内只做 DML,不做事务控制
存储过程里唯一可做的事务相关动作是错误捕获与 SIGNAL
你可以在过程里用声明式异常处理器,但目的不是“管理事务”,而是协助上层快速判断是否需要回滚。MySQL 存储过程没有 TRY...CATCH,只有 DECLARE HANDLER,且它不能替代应用层的事务决策。
- 用
DECLARE EXIT HANDLER FOR SQLEXCEPTION设置标志位或直接SIGNAL抛出错误,让调用方感知失败 - 避免在过程里写
ROLLBACK—— 如果连接已被外部开启事务,你的ROLLBACK会破坏上层语义 - 过程内检查
ROW_COUNT()或@@ERROR可辅助判断,但最终是否回滚,决定权必须在应用层
InnoDB 行锁 + 隔离级别才是长连接事务一致性的底层保障
连接可以复用,但事务一致性不靠连接“长”来保证,而靠 InnoDB 的 MVCC 和锁机制。如果忽略隔离级别和锁粒度,再严谨的事务包裹也救不了并发更新冲突。
- 默认
REPEATABLE READ在多数业务够用,但电商扣库存这类场景建议用SELECT ... FOR UPDATE显式加行锁,而非依赖 UPDATE 自动锁 - 避免在事务中做耗时操作(如 HTTP 请求、文件读写),否则长事务会持锁太久,引发死锁或阻塞
- 表必须是
InnoDB引擎——MyISAM完全不支持事务,哪怕你写了START TRANSACTION也无效
真正容易被忽略的点是:存储过程看起来“包住了一堆 SQL”,就误以为它天然具备事务上下文。其实它只是 SQL 批处理单元,事务边界永远在连接会话层面,且由第一个 START TRANSACTION 命令定义。长连接的价值在于复用开销,而不是延长事务寿命。


















