簡體   English   中英

SQL Server不會將觸發器事務回滾到保存點

[英]SQL Server does not rollback trigger transaction to savepoint

我在嘗試根據自己的觀點觸發交易時遇到問題。 這是我的DDL設置:

CREATE TABLE entity1 (
    id INT NOT NULL IDENTITY PRIMARY KEY,
    attr1 INT NOT NULL,
    attr2 INT NOT NULL
);
GO

CREATE TABLE entity2 (
    entity1_id INT NOT NULL FOREIGN KEY REFERENCES entity1(id),
    attr3 INT NOT NULL,
    attr4 INT NOT NULL
);
GO

CREATE VIEW my_view AS
SELECT attr1, attr2, attr3, attr4
FROM entity1 AS e1 
INNER JOIN entity2 AS e2 
ON e1.id = e2.entity1_id;
GO

CREATE TRIGGER tg_my_view_ins ON my_view
INSTEAD OF INSERT AS
BEGIN
    BEGIN TRY
        SAVE TRANSACTION here; -- checkpoint
        INSERT INTO entity1 (attr1, attr2) 
        SELECT attr1, attr2 FROM inserted;
        INSERT INTO entity2 (entity1_id, attr3, attr4) 
        SELECT SCOPE_IDENTITY(), attr3, attr4 FROM inserted;
    END TRY
    BEGIN CATCH
        ROLLBACK TRANSACTION here; -- rollback to checkpoint in case on an error
    END CATCH
END
GO

如您所見,我在觸發器中創建了一個保存點,並在出現任何錯誤的情況下進行了回滾(我假設約束錯誤也由TRY / CATCH塊處理)。 問題是,當我在事務內執行錯誤的插入操作時,觸發器錯誤處理塊不會回滾:

BEGIN TRY
    BEGIN TRANSACTION;

        -- successful insert
        INSERT INTO my_view (attr1, attr2, attr3, attr4) VALUES (1,2,3,4);
        SELECT * FROM entity1; -- one entity

        -- i wrap the bad insert into try/catch so the error is discarded, 
        -- but still rolled back
        BEGIN TRY
            INSERT INTO my_view (attr1, attr2, attr3) VALUES (3,2,1);
        END TRY
        BEGIN CATCH
        END CATCH;

        SELECT * FROM entity1; -- should only have one entity, but has two
    ROLLBACK; -- discard the whole transaction
END TRY
BEGIN CATCH
    ROLLBACK; -- discard the whole transaction in case of any errors
END CATCH;

結果輸出

我似乎無法以發生錯誤時不會創建孤立記錄的方式來設置觸發器。 我嘗試在觸發器中使用BEGIN TRANSACTION hereCOMMIT TRANSACTION here而不是SAVE TRANSACTION here ,但是沒有運氣。 處理觸發器中的約束錯誤的正確方法是什么?

如果可能的話,我想保持執行設置的狀態。 我創建並回滾事務以進行測試。 我將錯誤的插入文件包裝到try / catch塊中,以丟棄我知道應該發生的錯誤。

通過將錯誤日志記錄添加到catch塊中,可以弄清這種看似混亂的行為。 測試代碼的以下修改添加了錯誤日志記錄(以及其他一些改進),該錯誤記錄顯示了該過程中實際發生的情況:

begin try
  begin transaction;

  INSERT INTO dbo.my_view (attr1, attr2, attr3, attr4) VALUES (1,2,3,4);
  SELECT * FROM dbo.entity1;

  BEGIN TRY
  INSERT INTO dbo.my_view (attr1, attr2, attr3) VALUES (3,2,1);
  END TRY
  BEGIN CATCH
  -- Logging - inner CATCH
  select 'Inner', @@trancount, error_number(), error_message(), error_procedure(), error_line();
  END CATCH;

  select * from dbo.entity1;
  rollback;

end try
begin catch

  -- Logging - outer CATCH
  select 'Outer', @@trancount, error_number(), error_message(), error_procedure(), error_line();

  -- Conditional rollback, because some errors always terminate the transaction
  if @@trancount > 0
    rollback;

end catch;

如果您在完整觸發條件的情況下運行此代碼,則會看到內部CATCH捕獲到錯誤:

3931

當前事務無法提交,也不能回滾到保存點。 回滾整個事務。

通過錯誤號搜索導致該帖子出現類似問題。 Rutzky在他的回答中表明,此行為的罪魁禍首是XACT_ABORT會話選項,該選項顯然已默認設置為ON 如果您打算采用基於觸發器的體系結構,那么在觸發器內部關閉此選項將有幫助:

create or alter trigger dbo.tg_my_view_ins
on dbo.my_view
instead of insert as

-- Implicitly set to ON in triggers by default; makes error handling impossible
set xact_abort off;

begin try
  save transaction here;

  INSERT INTO dbo.entity1 (attr1, attr2) 
  SELECT attr1, attr2 FROM inserted;

  INSERT INTO dbo.entity2 (entity1_id, attr3, attr4) 
  SELECT e.id, attr3, attr4
  FROM inserted i
    -- The actual JOIN condidions should reference a natural key in the master table.
    -- This is just an example.
    inner join dbo.entity1 e on e.attr1 = i.attr1 and e.attr2 = i.attr2;

end try
begin catch

  if @@trancount > 0
    rollback transaction here;

end catch;
return;
GO

(再次,我已更正了您的代碼的其他幾個問題。)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM