我需要在每个触发器中将 XACT_ABORT 设置为 ON 吗?

Do I need to set XACT_ABORT to ON in every trigger?

这是我刚迁移到 SQL Server 2019 Express 的旧 SQL Server 2008 Express 数据库(实际上有五个)。一切似乎都很好,直到我的工作人员进来,我们到处都遇到错误。原来我们在触发器中有 RAISEERROR,即使我的兼容性似乎设置为 2008 (100),我们仍然收到错误。所以我升级到THROW。现在一切似乎都工作正常,但由于我不是 DBA,我担心我的升级会损坏一些数据或留下孤儿。这是触发器之一的示例:

USE [toddAPB]
GO
/****** Object:  Trigger [dbo].[T_tSaleLineItem_ITrig]    Script Date: 5/18/2021 1:32:55 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[T_tSaleLineItem_ITrig] ON [dbo].[tSaleLineItem] FOR INSERT AS
SET NOCOUNT ON
/* * PREVENT INSERTS IF NO MATCHING KEY IN 'tProduct' */
IF (SELECT COUNT(*) FROM inserted) !=
   (SELECT COUNT(*) FROM tProduct, inserted WHERE (tProduct.RecNumP = inserted.RecNumP))
    BEGIN
        ;THROW 44447, 'The record can''t be added or changed. Referential integrity rules require a related record in table ''tProduct''.',1;
        ROLLBACK TRANSACTION
    END

/* * PREVENT INSERTS IF NO MATCHING KEY IN 'tSale' */
IF (SELECT COUNT(*) FROM inserted) !=
   (SELECT COUNT(*) FROM tSale, inserted WHERE (tSale.RecNumS = inserted.RecNumS))
    BEGIN
        ;THROW 44447, 'The record can''t be added or changed. Referential integrity rules require a related record in table ''tSale''.',1;
        ROLLBACK TRANSACTION
    END

我是否需要在每个触发器(数百个)上放置 SET XACT_ABORT ON?我是不是该?每 THROW 之后我还需要 ROLLBACK TRANSACTION 吗?

在我调整到 THROW 之前,我遇到了“44447 附近的语法错误”错误。该行以前看起来更像这样:

RAISERROR 44447 'The record can''t be added or changed. Referential integrity rules require a related record in table ''tProduct''.'

感谢您的帮助。

其实你应该用外键约束来解决这个问题(感谢 Charlieface),但是如果那不切实际请继续阅读...

SET XACT_ABORT ON 是触发器的默认值。

OFF is the default setting in a T-SQL statement, while ON is the default setting in a trigger.

您还应该使用适当的 连接而不是隐式连接。我已经列出了如何使用正确终止的语句编写触发器。

ALTER TRIGGER [dbo].[T_tSaleLineItem_ITrig]
ON [dbo].[tSaleLineItem]
FOR INSERT AS
BEGIN
    SET NOCOUNT ON;

    /* PREVENT INSERTS IF NO MATCHING KEY IN 'tProduct' */
    IF (SELECT COUNT(*) FROM inserted) != (SELECT COUNT(*) FROM tProduct P INNER JOIN inserted I ON P.RecNumP = I.RecNumP)
    BEGIN
        THROW 44447, 'The record can''t be added or changed. Referential integrity rules require a related record in table ''tProduct''.', 1;
    END;

    /* PREVENT INSERTS IF NO MATCHING KEY IN 'tSale' */
    IF (SELECT COUNT(*) FROM inserted) != (SELECT COUNT(*) FROM tSale S INNER JOIN inserted I ON S.RecNumS = I.RecNumS)
    BEGIN
        THROW 44447, 'The record can''t be added or changed. Referential integrity rules require a related record in table ''tSale''.', 1;
    END;
END;

你当然可以继续使用 RAISERROR but it appears the syntax has changed since you originally wrote it - so you would have to correct that. Since you are having to modify them all anyway, moving to THROW 似乎合适。

我觉得你的完整性检查很有趣,由于我处理逻辑的方式,我会把它写成下面这样,但我认为它没有什么更好的。

IF EXISTS (
    SELECT 1
    FROM Inserted I
    WHERE NOT EXISTS (SELECT 1 FROM tProduct P WHERE P.RecNumP = I.RecNumP)
)