SQL 锁定 table 的触发器中的服务器错误与 SqlDataAdapter 和环境事务一起丢失
SQL Server errors in trigger that locks table lost with SqlDataAdapter and ambient transaction
好的,所以我 运行 遇到了一个相当奇怪的情况。我的情况有好几层。我还没有确定是否严格要求每一层,但这是发生了什么:
- C# 代码正在创建环境事务,
SqlConnection
会自动加入其中。
- C# 代码使用
SqlDataAdapter
将一行插入 table。
InsertCommand
正在引用一个存储过程。存储过程是一个简单的 INSERT
语句。
- 正在执行
INSERT
的 table 上有一个触发器 INSTEAD OF INSERT
。
- 触发器获得 table 上的独占锁。
- 触发器发生错误。
有了这个结合,错误就不会在 C# 代码中出现。但是,如果触发器 没有 获得 table 上的独占锁,则错误确实弥补了 C# 代码。
错误 是 实际发生的,但事实证明,在 SQL 服务器端,事务已中止。 C#代码并不知道事务已经中止,只有在 TransactionScope
的处理尝试 COMMIT TRANSACTION
.
时才会遇到错误
我已经创建了这个场景的最小复制:
https://github.com/logiclrd/TestErrorWhileLockedInTrigger
有没有人知道为什么会这样,以及如何恢复正确的错误处理行为?
所以,我对此做了更多测试。
我的第一个想法是,如果持有独占锁导致它压制错误,也许显式释放锁会解除压制?所以,我在我的概念验证中生成错误的语句周围放置了一个 TRY
/CATCH
,让它 ROLLBACK TRANSACTION
然后重新 THROW
,但是它什么都没做。
所以我的下一个想法是,RAISERROR
语句在严重级别为 20-25 时强制终止连接。我不确定这是否是一个理想的解决方案,因为它还会在发生这种情况时向 SQL 服务器事件日志写入一个条目。但是,它 确实 实现了让 SqlDataAdapter
在其 Update
命令期间看到错误的目标,而不是 C# 代码认为事务仍然处于活动状态并试图提交它。
有谁知道这种 "sledgehammer" 方法的其他潜在缺点,或者它是否可能是在这种情况下正确传播错误的唯一方法?
我已经确定问题的原因。
触发器中锁定 table 的语句如下所示:
SELECT TOP 0 *
FROM TableToTriggerAndLock WITH (TABLOCKX, HOLDLOCK)
虽然此 return 没有数据,但它 确实 return 一个(空)结果集。事实证明 SqlDataAdapter
class 只关心它在 TDS 流上返回的第一个结果集,因此 second 结果集中返回的错误是完全过去了。
把locking语句去掉,你把那个多余的结果集去掉,现在错误在第一个个结果集中
然后,解决方案是抑制结果集,我通过将锁定语句修改为:
DECLARE @Dummy INT
SELECT TOP 0 @Dummy = 1
FROM TableToTriggerAndLock WITH (TABLOCKX, HOLDLOCK)
希望这对那些使用 SqlDataAdapter
和更复杂的底层操作的人有所帮助。 :-)
好的,所以我 运行 遇到了一个相当奇怪的情况。我的情况有好几层。我还没有确定是否严格要求每一层,但这是发生了什么:
- C# 代码正在创建环境事务,
SqlConnection
会自动加入其中。 - C# 代码使用
SqlDataAdapter
将一行插入 table。 InsertCommand
正在引用一个存储过程。存储过程是一个简单的INSERT
语句。- 正在执行
INSERT
的 table 上有一个触发器INSTEAD OF INSERT
。 - 触发器获得 table 上的独占锁。
- 触发器发生错误。
有了这个结合,错误就不会在 C# 代码中出现。但是,如果触发器 没有 获得 table 上的独占锁,则错误确实弥补了 C# 代码。
错误 是 实际发生的,但事实证明,在 SQL 服务器端,事务已中止。 C#代码并不知道事务已经中止,只有在 TransactionScope
的处理尝试 COMMIT TRANSACTION
.
我已经创建了这个场景的最小复制:
https://github.com/logiclrd/TestErrorWhileLockedInTrigger
有没有人知道为什么会这样,以及如何恢复正确的错误处理行为?
所以,我对此做了更多测试。
我的第一个想法是,如果持有独占锁导致它压制错误,也许显式释放锁会解除压制?所以,我在我的概念验证中生成错误的语句周围放置了一个 TRY
/CATCH
,让它 ROLLBACK TRANSACTION
然后重新 THROW
,但是它什么都没做。
所以我的下一个想法是,RAISERROR
语句在严重级别为 20-25 时强制终止连接。我不确定这是否是一个理想的解决方案,因为它还会在发生这种情况时向 SQL 服务器事件日志写入一个条目。但是,它 确实 实现了让 SqlDataAdapter
在其 Update
命令期间看到错误的目标,而不是 C# 代码认为事务仍然处于活动状态并试图提交它。
有谁知道这种 "sledgehammer" 方法的其他潜在缺点,或者它是否可能是在这种情况下正确传播错误的唯一方法?
我已经确定问题的原因。
触发器中锁定 table 的语句如下所示:
SELECT TOP 0 *
FROM TableToTriggerAndLock WITH (TABLOCKX, HOLDLOCK)
虽然此 return 没有数据,但它 确实 return 一个(空)结果集。事实证明 SqlDataAdapter
class 只关心它在 TDS 流上返回的第一个结果集,因此 second 结果集中返回的错误是完全过去了。
把locking语句去掉,你把那个多余的结果集去掉,现在错误在第一个个结果集中
然后,解决方案是抑制结果集,我通过将锁定语句修改为:
DECLARE @Dummy INT
SELECT TOP 0 @Dummy = 1
FROM TableToTriggerAndLock WITH (TABLOCKX, HOLDLOCK)
希望这对那些使用 SqlDataAdapter
和更复杂的底层操作的人有所帮助。 :-)