存储过程本身不自动提供原子性,必须显式用 begin transaction / commit / rollback 包裹操作,否则每条语句单独提交,资金转移中途失败就会留下脏数据。

存储过程本身不自动提供原子性,必须显式用 BEGIN TRANSACTION / COMMIT / ROLLBACK 包裹操作,否则每条语句单独提交,资金转移中途失败就会留下脏数据。
MySQL 存储过程中必须手动开启事务
MySQL 默认 autocommit=1,意味着每条 DML 语句(INSERT、UPDATE、DELETE)都会立即提交。在资金类操作中,这等同于把转账拆成“扣 A”和“加 B”两个独立事务——A 扣成功但 B 加失败时,钱就丢了。
- 开头必须写
START TRANSACTION或BEGIN(二者等价),结尾配COMMIT; - 所有资金相关语句(如两笔
UPDATE)必须写在同一个事务块内; - 务必检查每步执行结果:用
ROW_COUNT()判断是否影响了预期行数,用GET DIAGNOSTICS捕获错误; - 一旦发现异常(比如余额不足、账户不存在),立刻
ROLLBACK并用RESIGNAL抛出错误,不能静默忽略。
SQL Server 中事务自动继承但需防隐式提交
SQL Server 存储过程默认运行在调用方的事务上下文中,看起来“天然原子”,但实际容易被隐式提交破坏。
- 避免在过程里调用
SET IMPLICIT_TRANSACTIONS ON,否则可能意外开启嵌套事务; - 禁用任何会触发隐式提交的操作:比如
CREATE TABLE、ALTER DATABASE、TRUNCATE TABLE(这些在 SQL Server 中会自动提交当前事务); - 如果过程被多个地方调用,要确认调用方是否已开启事务——没开的话,你的过程得自己
BEGIN TRAN; - 使用
XACT_STATE()判断当前事务状态:-1表示不可提交的失败事务,此时只能ROLLBACK,不能再COMMIT。
PostgreSQL 存储过程需显式控制事务边界
PostgreSQL 的函数(CREATE FUNCTION)默认在事务内部执行,但函数本身不能直接 COMMIT 或 ROLLBACK —— 除非声明为 SECURITY DEFINER 并配合 dblink 等扩展模拟自治事务,但这已脱离原事务一致性保障。
- 资金操作应封装在
DO块或显式事务中调用,而非依赖函数自动提交; - 推荐写法:外部应用或脚本先
BEGIN,再调用函数,最后COMMIT或ROLLBACK; - 函数内可用
EXCEPTION块捕获错误,但只能RAISE异常,不能自行回滚——回滚责任在调用层; - 慎用
SAVEPOINT:它只是子事务标记,ROLLBACK TO SAVEPOINT不影响外层事务,别误以为能替代主事务控制。
跨库/跨服务资金操作无法靠单库存储过程保证原子性
如果资金操作涉及 MySQL 和 Redis(比如扣余额+删缓存)、或调用支付网关 API,存储过程再严谨也无济于事——数据库事务管不了外部系统。
- 这类场景必须放弃“单过程原子性”幻想,改用最终一致性方案:本地事务 + 消息队列 + 补偿任务;
- 存储过程里禁止直接发 HTTP 请求或调用外部命令(
pg_background、sys_exec等),会阻塞事务、超时、且无法回滚网络动作; - 哪怕只操作同一数据库的多张表,也要确认它们属于同一事务引擎(例如 MySQL 中
MyISAM表不支持事务,混用即失效); - 批量资金操作(如发工资)务必测试锁等待行为:
UPDATE ... WHERE id IN (...)可能引发间隙锁冲突,导致死锁或超时,这不是原子性问题,但会让事务反复失败。
真正难的不是写 COMMIT,而是判断哪些操作必须捆进同一个事务、哪些该拆出去异步做——比如“扣款成功后发短信”就不该和扣款放一起,短信失败不该导致扣款回滚。











