使用SELECT-UPDATE模式时管理并发


25

假设您有以下代码(请忽略这很糟糕):

BEGIN TRAN;
DECLARE @id int
SELECT @id = id + 1 FROM TableA;
UPDATE TableA SET id = @id; --TableA must have only one row, apparently!
COMMIT TRAN;
-- @id is returned to the client or used somewhere else

在我看来,这不能正确地管理并发。仅仅因为您有一笔交易并不意味着其他人不会获得与您获得更新语句之前相同的值。

现在,将代码保持原样(我意识到这可以更好地作为单个语句处理,甚至可以使用自动增量/标识列更好地进行处理),有什么确定的方法可以使其正确处理并发并防止允许两个客户端获得相同竞争条件的竞争条件。 id值?

我非常确定,将WITH (UPDLOCK, HOLDLOCK)SELECT 添加到SELECT即可。该SERIALIZABLE事务隔离级别(因为它拒绝任何人阅读你做了什么,直到移植是在将似乎工作,以及UPDATE:这是假见马丁的答案)。真的吗?他们俩会平等地工作吗?是一个比另一个更好的选择吗?

想象一下,做比ID更新更合法的事情-基于需要更新的读取进行一些计算。可能涉及许多表,其中一些将要写入,而有些则不会。最佳做法是什么?

写完这个问题后,我认为锁定提示会更好,因为那样的话,您只锁定了所需的表,但是我很感谢任何人的投入。

PS:不,我不知道最佳答案,并且确实希望得到更好的理解!:)


仅作说明:您是否要阻止2个客户端读取相同的值或update基于过时的数据进行发行?如果是后者,则可以使用rowversioncolumn来检查要读取的行自读取以来是否未更改。
a1ex07

我们不希望第二个客户端在第一个客户端将旧的id值更新为新值之前获取旧的id值。它应该阻止。
ErikE 2012年

Answers:


11

仅解决SERIALIZABLE隔离级别方面。是的,这将起作用,但存在死锁风险。

两个事务都将能够同时读取该行。它们不会相互阻塞,因为它们将根据表结构采用对象S锁或索引RangeS-S锁,并且这些锁是兼容的。但是,当尝试获取更新所需的锁(分别是对象IX锁或索引RangeS-U)时,它们将彼此阻塞,这将导致死锁。

UPDLOCK相反,使用显式提示将序列化读取,从而避免了死锁的风险。


+1但是:对于堆表,即使具有更新锁,您仍然可以获得转换死锁:sqlblog.com/blogs/alexander_kuznetsov/archive/2009/03/11/…–
AK

奇怪,@ alex。我想这与引擎的竞争状况有关,引擎试图在实际UPDLOCK之前找到要锁定的东西……
ErikE 2012年

@ErikE-Alex文章中的转换死锁是将堆本身从转换IXX。有趣的是,没有行符合条件,因此没有行锁被取出。不确定为什么要完全X锁定。
马丁·史密斯

11

我认为对您来说最好的方法是将模块实际暴露给高并发性并亲自查看。有时仅使用UPDLOCK就足够了,并且不需要HOLDLOCK。有时sp_getapplock效果很好。我在这里不做任何笼统的声明-有时再添加一个索引,触发器或索引视图会改变结果。我们需要强调测试代码,并逐案查看。

在这里写了一些压力测试的例子

编辑:为了更好地了解内部原理,您可以阅读Kalen Delaney的书。但是,书籍可能像其他任何文档一样不同步。此外,要考虑的组合太多:六个隔离级别,多种类型的锁,群集/非群集索引以及谁还知道其他什么。那是很多组合。最重要的是,SQL Server是封闭源代码,因此我们无法下载源代码,对其进行调试等-这将是最终的知识来源。在下一个版本或Service Pack之后,其他任何内容可能都不完整或已过时。

因此,在没有自己的压力测试的情况下,您不应决定什么对您的系统有效。无论您阅读了什么,它都可以帮助您了解正在发生的事情,但是您必须证明阅读的建议对您有用。我认为没有人能为您做到。


9

在这种特殊情况下,向UPDLOCK锁添加锁SELECT确实可以防止异常。HOLDLOCK不需要添加,因为在事务期间将保持更新锁,但是我承认自己过去将其包括为一种(可能是不好的)习惯。

想象一下,做一些比ID更新更合法的事情,基于需要更新的读取进行一些计算。可能涉及许多表,其中一些将要写入,而有些则不会。最佳做法是什么?

没有最佳实践。您对并发控制的选择必须基于应用程序的要求。某些应用程序/事务需要像对数据库的专有所有权一样执行,以不惜一切代价避免异常和不准确。其他应用程序/事务可以容忍彼此之间的某种程度的干扰。

  • 在网上商店中检索产品的带状库存水平(<5、10 +,50 +,100 +)=脏读(不准确无关紧要)。
  • 在网上商店结帐时检查并减少库存水平=可重复读取(在销售之前,我们必须拥有库存,我们不得以负库存结束)。
  • 在银行的活期存款帐户和储蓄帐户之间移动现金=可序列化(不要误算或放错我的现金!)。

编辑:@AlexKuznetsov的评论促使我重新阅读了问题,并删除了答案中非常明显的错误。深夜发布时请注意自我。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.