sql server 中 begin transaction 不创建嵌套事务,仅增加 @@trancount;rollback 会回滚整个外层事务;子过程禁用 begin/commit/裸 rollback,应通过 @@trancount 判断并使用 save transaction;mysql 存储过程禁止 start transaction,唯一安全手段是 savepoint(需异常处理器且点名字面量),但不释放锁;推荐扁平化设计、应用层统一控事务、严格锁序与索引优化。

SQL Server 里写 BEGIN TRANSACTION 不等于嵌套事务
它只是给 @@TRANCOUNT 加 1,不是开新事务。所有操作仍归属最外层事务,ROLLBACK 会直接清空全部,不管当前 @@TRANCOUNT 是 2 还是 5。
常见错误现象:子过程里写 BEGIN TRANSACTION + ROLLBACK,结果外层插入的数据也丢了;或者报错 Msg 266, Level 16, State 2: Transaction count after EXECUTE indicates a mismatch。
- 子过程绝不该调用
BEGIN TRANSACTION、COMMIT或裸ROLLBACK - 若被单独调用,需靠
@@TRANCOUNT判断上下文:IF @@TRANCOUNT = 0 BEGIN TRANSACTION,否则用SAVE TRANSACTION sp_name -
SAVE TRANSACTION必须在已有事务中执行,否则报Msg 628 - 回滚到保存点后,
@@TRANCOUNT不变,仍需最终COMMIT或ROLLBACK
MySQL 存储过程中不能用 START TRANSACTION 模拟嵌套
START TRANSACTION 在过程内执行,会隐式提交前一个事务——不是挂起,是立刻落库并重开。你以为的“内层事务”,实际已把外层未提交的 INSERT 强制写入磁盘。
典型表现:调用方开启事务后进存储过程,过程里再 START TRANSACTION,接着查不到刚插的记录(已被提交+受隔离级别影响);或报 ERROR 1305 (42000): SAVEPOINT does not exist。
- 禁止在存储过程中写
START TRANSACTION或COMMIT -
SAVEPOINT是唯一可行手段,但必须先声明异常处理器:DECLARE EXIT HANDLER FOR SQLEXCEPTION要在SAVEPOINT sp_x之前 - 保存点名必须是字面量(如
sp_update_user),不能是变量@sp_name -
ROLLBACK TO SAVEPOINT不释放锁,长事务中易阻塞,需靠SELECT * FROM performance_schema.data_locks验证
SAVEPOINT 不是事务边界,只是回滚锚点
它不开启事务、不释放锁、不改变隔离行为。回滚到保存点后,已修改的行仍处于未提交状态,其他查询能否看到取决于当前隔离级别,且持有的行锁/间隙锁继续生效。
容易被忽略的点:高并发下频繁设点 + 回滚,锁会累积,轻则拖慢,重则触发 ERROR 1205 (HY000): Deadlock found when trying to get lock。
- 按业务语义设点,不是每条 SQL 都要
SAVEPOINT(例如“创建用户 + 分配角色”算一个逻辑块) - 批量操作必须分批,比如用
TOP (500)+ 循环,每批后COMMIT,避免单次锁住上万行 - 跨服务调用时,锁序必须全局约定并文档化(如统一按
accounts → transactions → audit_log顺序访问) - 检查执行计划,避开
Clustered Index Scan或Index Scan,它们是锁范围失控的信号
真正安全的做法:扁平化 + 应用层控事务
别让存储过程承担事务编排责任。把业务拆成原子操作(如 usp_ChargeAccount、usp_CreditAccount、usp_LogTransfer),由应用层用单个事务包裹,并严格控制执行顺序和锁范围。
存储过程只做一件事,开头加注释声明依赖:-- LOCK ORDER: accounts → transactions。复杂逻辑优先拆短事务,而不是堆 SAVEPOINT。
- 每个小过程只读/写固定几张表,禁止调用含外部依赖的操作(如
OPENQUERY、EXEC xp_cmdshell) - WHERE 条件字段必须有索引,且类型严格匹配(INT 字段别传字符串)
- ORM 中禁用
REQUIRES_NEW,它割裂原子性,外层回滚不影响内层已提交数据 - 事务上下文要带 trace_id,记录每次
BEGIN/SAVEPOINT/COMMIT的时机和@@TRANCOUNT值,方便排查静默失败
事务边界从来不在存储过程里,而是在调用它的那一层。保存点只是临时拐杖,不是替代方案。










