我在存储过程中使用事务时出现的问题

时间:2013-03-21 13:01:06

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

我现在遇到一个与SQL相关的问题,现在只是惹恼我:p。这是我的设置:

我有2个存储过程:

父存储过程称为GenerateAnnualPenalty

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

其次,对于Property迭代的每个GenerateAnnualPenalty,它会调用名为GenerateAnnualPenaltyForProperty的子存储过程。 @ApplyPenalty作为GenerateAnnualPenaltyForProperty的输入传递。 GenerateAnnualPenaltyForProperty确实使用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表中看到了Checkpoint 1和Checkpoint 2条目。

当为属性2调用GenerateAnnualPenaltyForProperty时,一切都很好:我在DebugLog表中看到了Checkpoint 1和Checkpoint 2条目。

当为属性3调用GenerateAnnualPenaltyForProperty时,会出现错误的情况:我只看到DebugLog表中的Checkpoint 1条目,而'Checkpoint 2`条目也应该可见! / p>

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

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

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

如果我禁用GenerateAnnualPenaltyForProperty中与SQL Transactions相关的所有代码,一切正常!纠正了上述问题情况。这是GenerateAnnualPenaltyForProperty的框架,其中已取出SQL事务代码,但它的工作原理如下:

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

2 个答案:

答案 0 :(得分:0)

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

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

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

答案 1 :(得分:0)

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