SQL Server嵌套事务隔离级别变更引发报错原因咨询
咱们先拆解下你遇到的两个错误的核心根源,再给出对应的解决思路:
一、错误原因分析
1. 快照隔离的硬性启动规则
SQL Server的**快照隔离(Snapshot Isolation)**有个严格要求:必须在事务启动之前就设置好这个隔离级别,绝对不能在一个已经运行的事务中途切换到快照隔离。
你的场景里,ReadCommittedIsolationLevel先启动了事务t1(默认用Read Committed隔离级别),之后才调用SnapShotIsolationLevel存储过程。此时整个执行上下文都挂在外层的t1事务里,你在这个已启动的事务中执行SET TRANSACTION ISOLATION LEVEL SNAPSHOT再尝试启动t2,直接违反了快照隔离的规则——当前活跃的事务(实际就是t1)并非以快照隔离级别启动,所以触发第一个错误:
数据库'MyDataBase'中的事务失败,因为语句在快照隔离级别下运行,但事务并非以快照隔离级别启动。事务启动后无法将其隔离级别更改为快照,除非事务最初是以快照隔离级别启动的。
2. 伪嵌套事务导致的回滚失败
SQL Server根本不支持真正的嵌套事务,所谓的“嵌套”只是用事务计数模拟的。当你在外层事务t1里执行BEGIN TRANSACTION t2,本质只是把事务计数加1,并没有创建一个独立的t2事务。
所以当内层CATCH块尝试执行ROLLBACK TRAN t2时,SQL Server找不到这个独立的t2事务(实际只有外层的t1存在),自然抛出第二个错误:
无法回滚t2。未找到该名称的事务或保存点。
二、解决思路
根据你的需求,有两种常用处理方式:
方式1:让快照事务独立运行
如果你希望SnapShotIsolationLevel的查询在纯粹的快照隔离事务中执行,就得确保它不嵌套在外层事务里。可以在外层存储过程调用它之前先清理当前事务,或者直接让外层事务用快照隔离级别:
方案A:先清理外层事务再调用
CREATE PROCEDURE ReadCommittedIsolationLevel AS BEGIN -- 先确保没有活跃事务,再执行快照隔离的存储过程 IF @@TRANCOUNT > 0 COMMIT TRANSACTION; BEGIN TRY EXEC SnapShotIsolationLevel; END TRY BEGIN CATCH PRINT ERROR_MESSAGE(); END CATCH END
方案B:外层事务直接用快照隔离
CREATE PROCEDURE ReadCommittedIsolationLevel AS BEGIN SET TRANSACTION ISOLATION LEVEL SNAPSHOT BEGIN TRANSACTION t1 BEGIN TRY EXEC SnapShotIsolationLevel; -- 内层会继承外层的快照隔离级别 COMMIT TRANSACTION t1; END TRY BEGIN CATCH PRINT ERROR_MESSAGE(); IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION t1; END CATCH END
方式2:调整内层存储过程的事务逻辑
如果必须在嵌套上下文里执行,那内层存储过程就不要单独启动事务,而是复用外层的事务上下文(同时要保证外层事务是快照隔离级别):
CREATE PROCEDURE SnapShotIsolationLevel AS BEGIN -- 不再单独启动事务,直接使用外层事务上下文 BEGIN TRY SELECT TOP 20 * FROM Orders ORDER BY 1 DESC; END TRY BEGIN CATCH PRINT ERROR_MESSAGE(); -- 把错误抛给外层,让外层统一处理回滚 THROW; END CATCH END
内容的提问来源于stack exchange,提问作者Offir

