【问题标题】:Problems occurring when I use Transactions in Stored Procedure在存储过程中使用事务时出现的问题
【发布时间】:2013-03-21 13:01:06
【问题描述】:

我遇到了一个与 SQL 相关的问题,现在让我很生气 :p。这是我的设置:

我有 2 个存储过程:

父存储过程称为GenerateAnnualPenalty

GenerateAnnualPenalty 中有一个 SELECT CURSOR,它遍历一系列名为 Properties 的对象,并且对于每个 Property,它确定是否需要应用 Penalty。这存储在一个名为@ApplyPenalty 的布尔变量中,它可以是01。此外,GenerateAnnualPenalty 中没有使用 SQL 事务。

其次,对于GenerateAnnualPenalty 迭代的每个Property,它调用一个名为GenerateAnnualPenaltyForProperty 的子存储过程。 @ApplyPenalty 作为输入传递给GenerateAnnualPenaltyForPropertyGenerateAnnualPenaltyForProperty 确实使用 SQL 事务(提交/回滚)。

我在名为DebugLog 的表中输入条目,以标记代码中是否已到达特定点。

这是孩子GenerateAnnualPenaltyForProperty的骨架:

ALTER PROCEDURE [RTS].[GenerateAnnualPenaltyForProperty]
@PROPERTY_ID numeric(18,0),
@ApplyPenalty int
AS

insert into DebugLog (DebugMessage1, DebugMessage2, DebugMessage3, DebugMessage4)
values
('Checkpoint 1 for Property:', @PROPERTY_ID, 'Apply Penalty:', @ApplyPenalty)

DECLARE @TRANSACTION_NAME varchar(50)
SET @TRANSACTION_NAME = 'GenerateAnnualPenaltyForProperty'

BEGIN TRANSACTION @TRANSACTION_NAME

BEGIN TRY


    insert into DebugLog (DebugMessage1, DebugMessage2, DebugMessage3, DebugMessage4)
    values
    ('Checkpoint 2 for Property:', @PROPERTY_ID, 'Apply Penalty:', @ApplyPenalty)

    IF @ApplyPenalty = 1
    BEGIN
        -- All main logic here !!!
    END

    COMMIT TRANSACTION @TRANSACTION_NAME
    RETURN 0
END TRY
BEGIN CATCH
    ROLLBACK TRANSACTION @TRANSACTION_NAME
    RETURN -1
END CATCH

问题(简​​介):

假设GenerateAnnualPenalty 以这个特定的顺序迭代了 4 个Properties(利用了这个问题):

要迭代的第一个属性:@PROPERTY_ID = 1

要迭代的第二个属性:@PROPERTY_ID = 2

要迭代的第三个属性:@PROPERTY_ID = 3

要迭代的第四个属性:@PROPERTY_ID = 4

假设对于属性 1、3 和 4,ApplyPenalty = 1,而对于属性 2,ApplyPenalty = 0

问题(主要症结):

当为属性 1 调用 GenerateAnnualPenaltyForProperty 时,一切正常:我在 DebugLog 表中看到检查点 1 和检查点 2 条目。

当为属性 2 调用 GenerateAnnualPenaltyForProperty 时,一切都再次正常:我在 DebugLog 表中看到了检查点 1 和检查点 2 条目。

当为属性 3 调用 GenerateAnnualPenaltyForProperty 时,会出现不正确的情况:我只看到 DebugLog 表中的检查点 1 条目,而“检查点 2”条目也应该是可见的!

当为属性 4 调用 GenerateAnnualPenaltyForProperty 时,它再次正确:我再次在 DebugLog 表中看到检查点 1 和检查点 2 条目。

所以问题只发生在ApplyPenalty = 1 的属性上,前提是在之前的迭代中ApplyPenalty = 0。在这种情况下,ApplyPenalty = 1 的属性会被视为ApplyPenalty = 0

禁用 SQL 事务代码可以解决问题,但为什么?:

如果我禁用GenerateAnnualPenaltyForProperty 中与 SQL 事务相关的所有代码,一切正常!纠正了上述有问题的情况。这是GenerateAnnualPenaltyForProperty的骨架,其中已取出SQL Transaction代码,它适用于:

ALTER PROCEDURE [RTS].[GenerateAnnualPenaltyForProperty]
@PROPERTY_ID numeric(18,0),
@ApplyPenalty int
AS

insert into DebugLog (DebugMessage1, DebugMessage2, DebugMessage3, DebugMessage4)
values
('Checkpoint 1 for Property:', @PROPERTY_ID, 'Apply Penalty:', @ApplyPenalty)


BEGIN TRY
    insert into DebugLog (DebugMessage1, DebugMessage2, DebugMessage3, DebugMessage4)
    values
    ('Checkpoint 2 for Property:', @PROPERTY_ID, 'Apply Penalty:', @ApplyPenalty)

    IF @ApplyPenalty = 1
    BEGIN
        -- All main logic here !!!
    END

    RETURN 0
END TRY
BEGIN CATCH
    RETURN -1
END CATCH

问题(???):

为什么会出现这种情况?为什么当我使用 SQL 事务提交/回滚到GenerateAnnualPenaltyForProperty 时,存储过程不适用于有问题的情况?

实际代码:

如果有人希望查看子存储过程的完整代码,请点击此处:https://gist.github.com/anonymous/5214236

【问题讨论】:

  • 我添加了一个 sql-server 标签来扩大您的受众。如果不正确,请将其替换为您正在使用的 db 类型。
  • 在属性 3 期间,您的“主逻辑”中可能出现错误,导致检查点 2 回滚。检查点 1 不在您的事务范围内,因此始终被插入。
  • 我会尽我所能摆脱基于光标的解决方案。我有一个游标示例,以及它的基于集合的替换示例:granadacoder.wordpress.com/2008/07/24/…
  • @JodyT,逻辑本身没有问题,因为如果我禁用 SQL 事务相关代码,我也会看到属性 3 的两个条目。而且,不会发生错误/异常。
  • 我没有看过你的代码,但是如果你正在迭代记录你做错了什么,你不应该迭代记录,你应该使用数据集。

标签: sql sql-server stored-procedures transactions sqltransaction


【解决方案1】:

您可以尝试将代码更改为:

BEGIN TRY

    -- Your code

END TRY

BEGIN CATCH

    DECLARE @ErrorMessage NVARCHAR(MAX);
    DECLARE @ErrorSeverity INT;
    DECLARE @ErrorState INT;
    SELECT @ErrorMessage = ERROR_MESSAGE(), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE();
    RAISERROR(@ErrorMessage, @ErrorSeverity, @ErrorState)

END CATCH

如果在事务期间确实发生了错误,并且完成了回滚,您将看到导致它的错误。

【讨论】:

  • 这并没有解决。我试过代码。正如预期的那样,结果是正确的,我没有看到任何错误。为了进一步确认,我运行了上述代码的第二个版本,在 catch 语句中添加了一个额外的 DebugLog 插入。返回值为0,没有错误,DebugLog中也没有错误条目。
【解决方案2】:

一个可能的解释是您有嵌套事务 - 例如,如果主逻辑调用一个存储过程,该过程也执行 BEGIN TRAN...COMMIT/ROLLBACK TRAN。如果内部事务执行 ROLLBACK,它将回滚到最外面的事务,因此在 Penalty 3 的情况下,代码似乎失败了。当您删除最外面的事务时,它只会回滚内部事务,使其看起来已经工作。

【讨论】:

  • 外部存储过程没有做任何与 SQL 事务相关的事情。父存储过程中没有开始/提交/回滚事务。这仅在子 SP 中完成,因为这是在单个属性级别进行实际处理的那个。
  • 我的意思是交易可能发生在标记为“--此处的所有主要逻辑!!!”的行内例如在可能被调用的其他 SP 中。
  • 是否有可能看到“-- 所有主要逻辑”,因为从你所说的其他一切来看,我看不出有任何理由。
  • 我已经三重检查了主逻辑中的任何地方都没有使用任何事务。子 SP 的完整代码可在此处获得:gist.github.com/anonymous/5214236 .. 虽然看起来有点吓人 :)
  • 谢谢,我看不出代码有问题。因此,当 BEGIN TRY 行前面有 BEGIN TRANSACTION 时,它的行为就好像没有到达 BEGIN TRY 行。您能否在 TRY 之前和 TRY 之后立即放置 PRINT 语句? (打印语句不应该被回滚,以防发生这种情况)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-07-04
  • 1970-01-01
  • 1970-01-01
  • 2015-05-20
  • 2019-02-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多