2009-11-10 24 views
3

我有一个问题,为什么有些SQL(在SQL Server 2005上运行)的行为是这样的。具体来说,我在更新期间进行了一次更改以减少锁争用,并且在我认为不会的情况下似乎正在工作。为什么SQL更新顶部显然减少锁定,即使没有记录更新?

原始代码:

我们有这样的更新语句,这是被应用到表300多万条记录:

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. 为什么第二个代码块允许其他的查询在表中获取,即使没有被更新的记录?
+0

我认为在它意识到没有要更新的行之前,仍然需要对表进行“SELECT”操作。 – 2009-11-10 20:32:43

+1

在什么事务隔离级别下? – 2009-11-10 21:18:25

回答

3

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

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

  1. 当在单个表或索引上获取5,000个锁时,SQL Server 2005将升级您的锁。有关于此的警告和例外,请参阅Lock Escalation (Database Engine)了解更多信息。
  2. 锁定提示,如ROWLOCK不要 防止锁定升级。
  3. “数据库引擎不会 将行或键范围锁升级为 页锁,但会将它们直接升级到表锁。”

因此,基于上述和您的描述,我猜想您的查询试图锁定行,正在升级到表级锁,并阻止对用户表的所有访问。您会注意到这种阻塞,因为表格很大时更新需要很长时间。

建议,避免锁升级是:

  1. 打碎大的操作小规模的行动(你这样做!)。
  2. 将您的查询调整为尽可能高效 。
  3. 作为最后的手段,您可以设置跟踪 标志1211以禁用锁升级 (不推荐!)。

有关更多详细信息,请参见How to resolve blocking problems that are caused by lock escalation in SQL Server

如果您想验证锁升级是怎么回事,您可以使用SQL Server Profiler并查看Lock:Escalation事件。

+0

锁升级很有意义。但是如果是这样的话,我会预期锁定升级会在原始声明和循环声明中发生。 它是否足够聪明,在循环语句中永远不会升级到表级锁,因为它知道它最多可以更改100行? – 2010-01-28 20:40:10

0
  1. 很明显。
  2. 这些条件非常重要ISNULL(email, '') = '' AND Foo = 'Y'

更新查找可能需要更新的所有行,这就是为什么即使没有要更新的行也需要相同的时间。

这是一个盲注,但您应该考虑在EmailFoo两个字段中添加一个索引(每个索引不是一个索引,而是两个索引)。

这是唯一的沉重查询你在这张桌子上做的?该表中的哪些索引?

+0

你可以通过在WHERE子句中删除'ISNULL'调用来进一步改进索引的使用。优化器会喜欢'WHERE(email IS NULL OR email ='')AND foo ='Y''。 – LukeH 2009-11-11 00:55:32

+0

@Luke:这是真的吗? isnull(email,'')=''不被它自己解释为(email是null或email ='')? – Ice 2009-11-16 21:53:43

0

似乎SQL Server正在根据TOP 200选择不同的锁,即使您指定了ROWLOCK。你能看到Management -> Activity MontiorLocks by Object下有什么区别吗?

0

你也应该考虑到重构的更新,如果从大的表,在我的经验,他们有更好的表现:

更新用户 美孚= 'N',酒吧= GETDATE()FROM (SELECT USER.ID FROM USER //可选NOLOCK提示,如果你不关心读取未提交。 WHERE COALESCE(EMAIL, '')= '' 和Foo = 'Y')d WHERE D.ID = USER.ID