为什么即使没有更新记录,SQL Update Top显然也会减少锁定?

时间:2009-11-10 20:25:28

标签: sql sql-server sql-server-2005

我有一个问题,为什么有些SQL(在SQL Server 2005上运行)的行为方式如此。具体来说,我做了一个更改,以减少更新期间的锁争用,它似乎在我认为不会的情况下工作。

原始代码:

我们有这样的更新声明,它正被应用于一个记录超过3,000,000条的表:

UPDATE  USER WITH (ROWLOCK)
SET Foo = 'N', Bar = getDate()
WHERE ISNULL(email, '') = ''
AND   Foo = 'Y'

你可能猜到,这似乎锁定了USER表一段时间。即使使用ROWLOCK提示,对USER运行查询和更新的其他作业也会阻塞,直到完成。这对于这个特定的应用程序来说是不可接受的,所以我想我会通过让update语句一次只更新100条记录来应用我读过的技巧。这会让其他查询偶尔有机会到场。

改进代码:

DECLARE @LOOPAGAIN AS BIT;
SET @LOOPAGAIN = 1;

WHILE @LOOPAGAIN = 1
  BEGIN
    UPDATE TOP (100) USER WITH (ROWLOCK)
    SET Foo = 'N', Bar = getDate()
    WHERE ISNULL(email, '') = ''
    AND   Foo = 'Y'

    IF @@ROWCOUNT > 0
      SET @LOOPAGAIN = 1
    ELSE
      SET @LOOPAGAIN = 0
  END

这就是诀窍。我们的更新完成了它的工作,其他查询能够得到满足。一切都是快乐和光明。

神秘:

我理解当表中有许多记录需要更新时,这是如何改善性能的。通过在每100次更新后快速浏览循环,它可以让其他查询有机会进入桌面。谜团是,即使没有受更新影响的记录,这个循环也会产生相同的效果!

我们第二次运行原始查询时,它只会运行一小部分时间(例如30秒左右),但即使没有更改记录,它也会在此期间锁定表。但是将查询放在带有“TOP(100)”子句的循环中,即使它花了很长时间没有做任何事情,它也为其他查询释放了表格!

我对此感到非常惊讶。谁能告诉我:

  1. 如果我刚刚说的话完全清楚,
  2. 为什么第二个代码块允许其他查询进入表中,即使没有更新记录?

4 个答案:

答案 0 :(得分:3)

这听起来像锁定升级的经典案例。

在第一个场景中,您正在更新看起来可能是3,000,000行表中的大量记录。有两件重要的事情需要考虑:

  1. 当在单个表或索引上获取5,000个锁时,SQL Server 2005将升级您的锁。有一些警告和例外,请参阅Lock Escalation (Database Engine)以获取更多信息。
  2. 锁定提示,如ROWLOCK则不会 防止锁升级。
  3. “数据库引擎没有 将行或键范围锁升级到 页面锁定,但升级它们 直接到表锁。“
  4. 基于上面和你的描述,我猜你的查询,试图锁定行,正在升级到表级锁,并阻止对User表的所有访问。您注意到此阻塞,因为更新需要很长时间,因为表很大。

    避免锁定升级的建议如下:

    1. 将大型操作分解为较小的操作(您已完成此操作!)。
    2. 调整查询效率 尽可能。
    3. 作为最后的手段,您可以设置跟踪 标志1211以禁用锁升级 (不推荐!)。
    4. 有关详细信息,请参阅How to resolve blocking problems that are caused by lock escalation in SQL Server

      如果要验证锁升级是否正在发生,可以使用SQL Server Profiler并查看Lock:Escalation事件。

答案 1 :(得分:0)

  1. 很明显。
  2. 这些条件非常重要ISNULL(email, '') = '' AND Foo = 'Y'
  3. 更新会查找可能需要更新的所有行,这就是为什么即使没有要更新的行,它也总是需要同一时间。

    这是一个盲目的镜头,但您应该考虑在EmailFoo字段中添加索引(不是每个字段都有一个索引,两个索引都是一个)。

    这是您在此表上执行的唯一查询吗?这个表中有哪些索引?

答案 2 :(得分:0)

似乎SQL Server正在基于TOP 200选择不同的锁,即使您指定了ROWLOCK。您能否在Management -> Activity Montior下的Locks by Object看到任何差异?

答案 3 :(得分:0)

您还应该考虑重构一下来自大表的更新,根据我的经验,它们表现更好:

更新用户 Foo ='N',Bar = getDate() FROM(SELECT USER.ID FROM USER //可选NOLOCK提示如果你不关心read uncommitted。 COALESCE(EMAIL,'')=''和Foo ='Y')D WHERE D.ID = USER.ID