我正在研究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
输出:
第1项的库存余额现为负数。我理解为什么库存显示为负数,因为窗口2中的事务不会阻止窗口3中的事务,但我找不到任何关于如何在事务中使用这种验证逻辑的引用,但仍然能够回滚使用快照隔离时的整个事务。根据我的理解,Oracle使用开箱即用的乐观并发,所以我想会有办法解决这个问题。是否有任何可用于替代此模式的模式可用于SQL Server中的快照隔离级别?
据我所知,如果我只是将隔离级别更改为READ COMMITTED,那么一切正常(除非在数据库上启用了SET READ_COMMITTED_SNAPSHOT ON),但我正在寻找可以产生相同结果的乐观并发模式。
答案 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