【发布时间】:2013-03-21 13:01:06
【问题描述】:
我遇到了一个与 SQL 相关的问题,现在让我很生气 :p。这是我的设置:
我有 2 个存储过程:
父存储过程称为GenerateAnnualPenalty。
GenerateAnnualPenalty 中有一个 SELECT CURSOR,它遍历一系列名为 Properties 的对象,并且对于每个 Property,它确定是否需要应用 Penalty。这存储在一个名为@ApplyPenalty 的布尔变量中,它可以是0 或1。此外,GenerateAnnualPenalty 中没有使用 SQL 事务。
其次,对于GenerateAnnualPenalty 迭代的每个Property,它调用一个名为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 表中看到检查点 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