【发布时间】:2010-04-09 10:15:30
【问题描述】:
我有一个几乎总是遵循的模式,如果我需要在事务中结束一个操作,我会这样做:
BEGIN TRANSACTION
SAVE TRANSACTION TX
-- Stuff
IF @error <> 0
ROLLBACK TRANSACTION TX
COMMIT TRANSACTION
过去这对我很有帮助,但是在使用这种模式多年(并复制粘贴上面的代码)之后,我突然发现了一个让我大吃一惊的缺陷。
很多时候,我会有一个存储过程调用其他存储过程,所有这些都使用相同的模式。我发现(以我的成本)是因为我在任何地方都使用相同的保存点名称,所以我可能会遇到我的外部事务被部分提交的情况——这与我试图实现的原子性正好相反.
我整理了一个展示问题的示例。这是一个批次(没有嵌套的存储过程),因此看起来有点奇怪,因为您可能不会在同一批次中两次使用相同的保存点名称,但我的真实场景太令人困惑而无法发布。
CREATE TABLE Test (test INTEGER NOT NULL)
BEGIN TRAN
SAVE TRAN TX
BEGIN TRAN
SAVE TRAN TX
INSERT INTO Test(test) VALUES (1)
COMMIT TRAN TX
BEGIN TRAN
SAVE TRAN TX
INSERT INTO Test(test) VALUES (2)
COMMIT TRAN TX
DELETE FROM Test
ROLLBACK TRAN TX
COMMIT TRAN TX
SELECT * FROM Test
DROP TABLE Test
当我执行此操作时,它会列出一条记录,其值为“1”。换句话说,即使我回滚了我的外部事务,表中还是添加了一条记录。
发生的事情是外层的ROLLBACK TRANSACTION TX 回滚到内层的最后一个SAVE TRANSACTION TX。现在我把这些都写出来了,我可以看到它背后的逻辑:服务器正在回顾日志文件,将其视为线性事务流;它不理解事务嵌套所隐含的嵌套/层次结构(或者,在我的实际场景中,调用其他存储过程)。
所以,很明显,我需要开始使用唯一的保存点名称,而不是到处盲目地使用“TX”。但是 - 这就是我最终要说到的地方 - 有没有办法以可复制的方式做到这一点,以便我 可以 仍然在任何地方使用相同的代码?我可以以某种方式即时自动生成保存点名称吗?做这种事情是否有惯例或最佳实践?
每次开始交易时想出一个唯一的名称并不难(可以基于 SP 名称或类似名称),但我确实担心最终会出现冲突 - 你不会了解它,因为它只是默默地破坏了您的数据,而不是导致错误... :-(
【问题讨论】:
标签: sql sql-server tsql transactions