【问题标题】:How to add transaction for 2 triggers如何为 2 个触发器添加事务
【发布时间】:2021-05-02 23:22:29
【问题描述】:

我有 2 个存储过程,它们将更新 2 个表并激活 2 个触发器

CREATE PROCEDURE UP_CheckOut
    @idBill INT, 
    @finalPrice INT
AS 
    UPDATE DBO.Bill 
    SET Status = N'Finished', 
        FinalPrice = @finalPrice 
    WHERE ID = @idBill 
      AND Status = N'Not Finished'
GO

CREATE PROCEDURE UP_ModifyBonusPoint_AfterCheckOut
     @used INT, 
     @get INT, 
     @phoneNum VARCHAR(100)
AS 
     UPDATE DBO.CustomerAccount 
     SET BonusPoint = BonusPoint - @used + @get, 
         LastBuyDate = GETDATE() 
     WHERE PhoneNum = @phoneNum
GO

CREATE TRIGGER UT_BillCheckOut
ON dbo.Bill 
FOR UPDATE
AS 
BEGIN
    DECLARE @check INT = 0

    SELECT @check = COUNT(*) 
    FROM Inserted 
    WHERE Status = N'Finished'

    IF @check > 0 
        EXEC UP_ModifyBonusPoint_AfterCheckOut -- I still stuck here because I don't know how to do
END
GO

CREATE TRIGGER UT_CustomerCheckOut
ON dbo.CustomerAccount
FOR UPDATE
AS 
BEGIN
    DECLARE @check INT = 0

    SELECT @check = COUNT(*) 
    FROM Inserted

    IF @check = 0 
        ROLLBACK UP_CheckOut -- I still stuck here because I don't know how to do
END
GO

我希望流程是这样的:

  • 检查账单(更改状态)-> 触发其插入
  • 如果成功,更新客户表(添加购买点)-> 触发其插入
  • 如果不成功,则回滚所有工作。

但我不知道添加事务以进行回滚。还是有另一种方法可以在 SQL 中解决这个问题?

【问题讨论】:

  • 它只回滚那个触发器(UT_CustomerCheckOut),但我希望它回滚所有,甚至是第一个过程(UP_CheckOut)
  • 提示:在检查是否存在一行或多行时,使用exists 比获取确切的count 然后将其与零进行比较更有效。
  • @HABO 如果它在IF 中使用,那么它 通常优化为EXISTS,这里显然不是因为变量,但你是对的最好不要依赖它
  • @HưngPhạm 我鼓励你阅读when someone answers

标签: sql sql-server tsql triggers transactions


【解决方案1】:

触发器中的回滚将显示不同的错误:The transaction ended in the trigger,这不是很有用。

相反,最好在触发器中写入THROW 语句以引发错误。

那么在外层过程中,我们可以写SET XACT_ABORT ON;,这样会导致从触发器创建的隐式事务在发生错误时自动回滚。

【讨论】:

  • 如果除了回滚之外还想进行任何处理,则可以在外部过程中使用 try/catch。
【解决方案2】:

与其依赖更新创建的隐式事务,不如在您的第一个存储过程中创建显式事务。然后调用ROLLBACK 将回滚整个事务。否则,您需要确保提交事务,但前提是事务仍处于打开状态。

CREATE PROC UP_CheckOut
(
    @idBill INT
    , @finalPrice INT
)
AS
BEGIN
    SET NOCOUNT, XACT_ABORT ON;

    BEGIN TRAN;

    UPDATE DBO.Bill SET
        Status = N'Finished'
        , FinalPrice = @finalPrice
    WHERE ID = @idBill AND Status = N'Not Finished';

    -- If there is still a transaction open, commit it
    -- If there is not it means a trigger rolled it back
    IF @@TRANCOUNT > 0 BEGIN
        COMMIT;
    END;

    RETURN 0;
END;
GO

注意:如果不存在交易,我会倾向于在此时向用户抛出错误。

【讨论】:

  • 我认为这不会像你想象的那样工作,因为 SQL Server 会在触发器结束时抛出一个错误,提示“事务在触发器中结束”
  • @Charlieface 嗯,但如果是这样的话,你难道不希望整个事务在不需要做任何事情的情况下回滚吗?
  • 确实,这就是为什么大多数人只是在一个触发器中设置XACT_ABORT_ON 然后THROW 来解决整个问题。
猜你喜欢
  • 1970-01-01
  • 2021-05-25
  • 1970-01-01
  • 1970-01-01
  • 2012-08-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多