SQL Server не выполняет откат транзакции триггера до точки сохранения - PullRequest
2 голосов
/ 13 марта 2019

Я столкнулся с проблемой, пытаясь настроить транзакцию в триггере моего просмотра.Вот мои настройки 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;

result output

Кажется, я не могу настроитьвызвать способ, которым он не будет создавать потерянные записи в случае ошибки.Я попытался использовать BEGIN TRANSACTION here и COMMIT TRANSACTION here в моем триггере вместо SAVE TRANSACTION here, но безуспешно.Как правильно обрабатывать ошибки ограничения в триггерах?

Настройка выполнения, которую я хотел бы сохранить, если это возможно.Я создаю и откатываю транзакцию для целей тестирования.Я обернул неверную вставку в блок try / catch, чтобы отменить ошибку, которая, как я знаю, должна произойти.

1 Ответ

3 голосов
/ 13 марта 2019

Это, казалось бы, запутанное поведение можно прояснить, добавив регистрацию ошибок в ваши блоки 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

Текущая транзакция не может быть зафиксирована и не может быть откатлена до точки сохранения.Откатите всю транзакцию.

Поиск по номеру ошибки приводит к этой записи с похожим вопросом.В своем ответе Руцки показывает, что виновником такого поведения является опция сеанса 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

(Опять же, я исправил несколько других проблем с вашим кодом.)

...