快照隔离 - 在事务中执行验证,同时保持回滚功能

时间:2014-04-16 21:37:46

标签: sql sql-server transactions sql-server-2008-r2 optimistic-locking

我正在研究SQL Server Snapshot隔离级别。对于简单的更新,事情似乎很简单,你可以找到很多如何处理的例子。但是,对于依赖于事务结束时的数据验证检查的逻辑,如果验证失败,我仍然无法使用仍允许我们进行ROLLBACK更改的模式。

以基本库存表为例,我们希望确保不允许库存余额为负数。我们会在交易结束时检查以确保余额不是负数,如果是,那么ROLLBACK会抛出错误。

下面是一个SQL脚本示例。 注意:您的数据库必须启用快照隔离才能使此示例正常工作。可以通过以下命令启用它:

ALTER DATABASE MyDatabase SET ALLOW_SNAPSHOT_ISOLATION ON;

WINDOW 1:使用库存中的一个项目创建库存表:

IF object_id('dbo.TEST_InventoryActivity') IS NOT NULL
    DROP TABLE dbo.TEST_InventoryActivity
GO

CREATE TABLE dbo.TEST_InventoryActivity
(   activityID int not null primary key identity
    , itemID int not null
    , inOrOut char(1) not null  
    , quantity int not null
    , modBy varchar(128) not null
    , modDate datetime not null default getdate()
)
go 

INSERT INTO TEST_InventoryActivity(itemID, inOrOut, quantity, modBy)
VALUES (1,'I',100, 'setups')
;
--show all records
SELECT i.*
FROM TEST_InventoryActivity i 
;
--show inventory balances
SELECT i.itemID, inventoryBalance = sum(i.quantity)
FROM TEST_InventoryActivity i 
GROUP BY i.itemID
;

WINDOW 2:现在打开一个新的查询窗口并执行以下命令,该命令不会提交(这将用于模拟同时发生的2个事务)。

SET TRANSACTION ISOLATION LEVEL SNAPSHOT;

BEGIN TRAN;

DECLARE @itemID int, @quantity int;
set @itemID = 1;
set @quantity = -75;

insert into TEST_InventoryActivity(itemID, inOrOut, quantity, modBy)
values(@itemID,'O', @quantity, 'test 1') --use Item 1
;

IF EXISTS(
    SELECT i.itemID, sum(i.quantity)
    FROM TEST_InventoryActivity i 
    WHERE i.itemID = @itemID
    GROUP BY i.itemID
    HAVING sum(i.quantity) < 0
)
BEGIN
    ROLLBACK;
    RAISERROR(N'Not enough remaining inventory', 16, 1);
    RETURN;
END
;

WINDOW 3:现在打开另一个新窗口并执行以下操作,这将导致库存为负。

SET TRANSACTION ISOLATION LEVEL SNAPSHOT;

BEGIN TRAN;

DECLARE @itemID int, @quantity int;
set @itemID = 1;
set @quantity = -50;

insert into TEST_InventoryActivity(itemID, inOrOut, quantity, modBy)
values(@itemID,'O', @quantity, 'test 2') --use Item 1
;

IF EXISTS(
    SELECT i.itemID, sum(i.quantity)
    FROM TEST_InventoryActivity i 
    WHERE i.itemID = @itemID
    GROUP BY i.itemID
    HAVING sum(i.quantity) < 0
)
BEGIN
    ROLLBACK;
    RAISERROR(N'Not enough remaining inventory', 16, 1);
    RETURN;
END
;

COMMIT;

WINDOW 2:现在返回Window 2并提交交易。

COMMIT;

当你检查第1项的库存余额时,它现在是-25,所以我们的验证不起作用,因为Window 3没有引用窗口2中发生的任何事情。

--show all records
SELECT i.*
FROM TEST_InventoryActivity i 
;
--show inventory balances
SELECT i.itemID, inventoryBalance = sum(i.quantity)
FROM TEST_InventoryActivity i 
GROUP BY i.itemID

输出:

enter image description here

第1项的库存余额现为负数。我理解为什么库存显示为负数,因为窗口2中的事务不会阻止窗口3中的事务,但我找不到任何关于如何在事务中使用这种验证逻辑的引用,但仍然能够回滚使用快照隔离时的整个事务。根据我的理解,Oracle使用开箱即用的乐观并发,所以我想会有办法解决这个问题。是否有任何可用于替代此模式的模式可用于SQL Server中的快照隔离级别?

据我所知,如果我只是将隔离级别更改为READ COMMITTED,那么一切正常(除非在数据库上启用了SET READ_COMMITTED_SNAPSHOT ON),但我正在寻找可以产生相同结果的乐观并发模式。

1 个答案:

答案 0 :(得分:1)

尝试在READCOMMITTED查询...

上使用EXISTS锁定提示
IF      EXISTS(SELECT   i.itemID, 
                        sum(i.quantity)
                FROM    TEST_InventoryActivity i With (READCOMMITTED)
                WHERE   i.itemID = @itemID
                GROUP   BY i.itemID
                HAVING  sum(i.quantity) < 0)
BEGIN
        ROLLBACK;
        RAISERROR(N'Not enough remaining inventory', 16, 1);
        RETURN;
END
ELSE
BEGIN
        COMMIT;
END

我在整个执行过程中在一堆不同的地方使用了这个查询,以确认READCOMMITTED表提示(赞美此post)不会改变事务的隔离级别......

SELECT  CASE transaction_isolation_level 
        WHEN 0 THEN 'Unspecified' 
        WHEN 1 THEN 'ReadUncommitted' 
        WHEN 2 THEN 'ReadCommitted' 
        WHEN 3 THEN 'Repeatable' 
        WHEN 4 THEN 'Serializable' 
        WHEN 5 THEN 'Snapshot' END AS TRANSACTION_ISOLATION_LEVEL 
FROM    sys.dm_exec_sessions 
WHERE   session_id = @@SPID