我有一个问题,为什么有些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)”子句的循环中,即使它花了很长时间没有做任何事情,它也为其他查询释放了表格!
我对此感到非常惊讶。谁能告诉我:
答案 0 :(得分:3)
这听起来像锁定升级的经典案例。
在第一个场景中,您正在更新看起来可能是3,000,000行表中的大量记录。有两件重要的事情需要考虑:
基于上面和你的描述,我猜你的查询,试图锁定行,正在升级到表级锁,并阻止对User表的所有访问。您注意到此阻塞,因为更新需要很长时间,因为表很大。
避免锁定升级的建议如下:
有关详细信息,请参阅How to resolve blocking problems that are caused by lock escalation in SQL Server。
如果要验证锁升级是否正在发生,可以使用SQL Server Profiler并查看Lock:Escalation事件。
答案 1 :(得分:0)
ISNULL(email, '') = '' AND Foo = 'Y'
。更新会查找可能需要更新的所有行,这就是为什么即使没有要更新的行,它也总是需要同一时间。
这是一个盲目的镜头,但您应该考虑在Email
和Foo
字段中添加索引(不是每个字段都有一个索引,两个索引都是一个)。
这是您在此表上执行的唯一重查询吗?这个表中有哪些索引?
答案 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