简单SQL Update语句不是事务性的
使用SQL 2016 我有一张订单表:简单SQL Update语句不是事务性的,sql,sql-server,tsql,sql-update,transactional,Sql,Sql Server,Tsql,Sql Update,Transactional,使用SQL 2016 我有一张订单表: OrderID int identity NumberOfItems int 和项目表: ItemId int identity OrderId int DateUpdated datetime 创建订单并通过标识分配订单ID。然后我必须从Items表中为它分配“最新的”“NumberOfItems”项。最新鲜的意思是,根据DateUpdate,它们是最近更新的。通过将项目的OrderId更新为相关的OrderId来“分配”项目 我使用此SQL以事
OrderID int identity
NumberOfItems int
和项目表:
ItemId int identity
OrderId int
DateUpdated datetime
创建订单并通过标识分配订单ID。然后我必须从Items表中为它分配“最新的”“NumberOfItems”项。最新鲜的意思是,根据DateUpdate,它们是最近更新的。通过将项目的OrderId更新为相关的OrderId来“分配”项目
我使用此SQL以事务方式分配项(@OrderID和@NumberOfItems是输入参数):
应该很好吧?这应该以事务方式将项目分配给订单,并且无论此语句在服务器上运行的频率或并发程度如何,同一项目一旦被分配,就不应该重新分配给另一个订单。由于所使用的单个UPDATE语句具有必要的事务性,因此几乎所有实现的关系数据库都应该保证这一点
好吧,它已经以这种方式运作了好几千万个订单。然后,昨天晚上,两个订单相隔约50毫秒(这对于本应用程序来说不是特别接近),一个项目分配给OrderN,然后相同的项目重新分配给OrderN+1
什么可能导致这种情况发生?从技术上讲,对Item表有两个调用。 尝试此重构查询,该查询对单个调用执行相同的操作:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
;WITH LimitedUpdate AS (
SELECT TOP(@NumberOfItems) i.OrderId
FROM Items i WITH (UPDLOCK)
WHERE i.OrderId IS NULL
ORDER BY i.DateStatusUpdated DESC
)
UPDATE LimitedUpdate SET OrderId = @OrderId
;
保留原始查询:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
UPDATE Items WITH (UPDLOCK)
SET OrderId = @OrderId
WHERE ItemId IN
(SELECT TOP(@NumberOfItems) ItemId FROM Items WITH (UPDLOCK)
WHERE OrderId IS NULL -- not already assigned
ORDER BY DateStatusUpdated DESC -- freshest first
)
;
从技术上讲,对Item表有两个调用。 尝试此重构查询,该查询对单个调用执行相同的操作:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
;WITH LimitedUpdate AS (
SELECT TOP(@NumberOfItems) i.OrderId
FROM Items i WITH (UPDLOCK)
WHERE i.OrderId IS NULL
ORDER BY i.DateStatusUpdated DESC
)
UPDATE LimitedUpdate SET OrderId = @OrderId
;
保留原始查询:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
UPDATE Items WITH (UPDLOCK)
SET OrderId = @OrderId
WHERE ItemId IN
(SELECT TOP(@NumberOfItems) ItemId FROM Items WITH (UPDLOCK)
WHERE OrderId IS NULL -- not already assigned
ORDER BY DateStatusUpdated DESC -- freshest first
)
;
readcommitted
隔离级别不能提供您想要的保证,因为您已经发现了自己的困难
简而言之,引擎首先执行SELECT
part,而不锁定表/行。
它仅在执行更新
阶段时锁定表/行,但此步骤稍后执行
因此,同时运行的两个查询可能会读取相同的行,然后尝试更新
无论您如何尝试重写查询,始终会有两个独立的阶段-选择
,然后更新
。您可以在执行计划中看到它们
“简单的解决方法”是对整个查询使用SERIALIZABLE
隔离级别,但这可能有些过分,一些提示(例如UPDLOCK
可能就足够了)
不过,我对这些提示没有太多经验。
readcommitted
隔离级别并不能提供您想要的保证,因为您已经发现了自己的困难所在
简而言之,引擎首先执行SELECT
part,而不锁定表/行。
它仅在执行更新
阶段时锁定表/行,但此步骤稍后执行
因此,同时运行的两个查询可能会读取相同的行,然后尝试更新
无论您如何尝试重写查询,始终会有两个独立的阶段-选择
,然后更新
。您可以在执行计划中看到它们
“简单的解决方法”是对整个查询使用SERIALIZABLE
隔离级别,但这可能有些过分,一些提示(例如UPDLOCK
可能就足够了)
不过,我对这些提示没有太多经验。DB?Read Committed上的隔离级别是什么。DB?Read Committed上的隔离级别是什么?Read Committed。不过,这些都在UPDATE语句中,没有脏读。我给出的SQL应该以事务方式执行。几十年来,我一直在使用类似的技术,完全没有任何问题。我看不出这会有什么不同。哦,你编辑并添加了UPDLOCK:-)如果我只是想修改我现有的SQL,而不使用WITH子句,我会将UPDLOCK放在更新项之后,还是放在SELECT或两者之后?正如Vladimir正确地说的那样,它只会在可序列化隔离级别下工作。我已经测试了一些东西-可能有些过火,但是使用(XLOCK,HOLDLOCK)肯定会有帮助-但它是独占锁,所以请注意。虽然它都在UPDATE语句中,并且没有脏读。我给出的SQL应该以事务方式执行。几十年来,我一直在使用类似的技术,完全没有任何问题。我看不出这会有什么不同。哦,你编辑并添加了UPDLOCK:-)如果我只是想修改我现有的SQL,而不使用WITH子句,我会将UPDLOCK放在更新项之后,还是放在SELECT或两者之后?正如Vladimir正确地说的那样,它只会在可序列化隔离级别下工作。我已经测试了一些东西-可能有些过火,但是使用(XLOCK,HOLDLOCK)肯定会有帮助-但它是独占锁,所以请注意。嗯。。。不过,这一切都在一个UPDATE语句中。我不认为两个并发执行的查询应该能够更新相同的行,因为更新将发出写锁并阻止读取任何其他SELECT的未提交数据。如果这句话有什么根本性的错误,我想我会在几亿笔交易中看到问题不止一次,这就是问题所在。是的,计划中的
UPDATE
操作符将锁定行/表,但此锁定位于更新之前,而不是整个查询开始之前。查询的第一个阶段是SELECT
,在这个阶段中还没有锁定。这就是为什么在您的案例中需要UPDLOCK
hint或同等提示的原因。您需要明确地告诉引擎,您希望在读取阶段将锁放在开始位置,并将锁一直保持到语句/事务结束。在默认的readcommitted
隔离级别下,锁会被保留很短的时间。“single statement”并不意味着从并发性的角度来看,在这个语句中所做的一切都是原子的。这项声明有几个要点