Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/sql/86.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
简单SQL Update语句不是事务性的_Sql_Sql Server_Tsql_Sql Update_Transactional - Fatal编程技术网

简单SQL Update语句不是事务性的

简单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以事

使用SQL 2016

我有一张订单表:

 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”并不意味着从并发性的角度来看,在这个语句中所做的一切都是原子的。这项声明有几个要点