单个语句(DML,DDL等)本身就是事务。因此,是的,在循环的每次迭代之后(技术上:在每个语句之后),该UPDATE
语句更改的任何内容都已自动提交。
当然总会有例外,对吧?可以通过SET IMPLICIT_TRANSACTIONS启用隐式事务,在这种情况下,第一条UPDATE
语句将启动您必须进行COMMIT
或ROLLBACK
结束的事务。这是一个会话级别设置,在大多数情况下默认情况下为OFF。
我们是否需要添加显式的BEGIN TRANSACTION / END TRANSACTION语句,以便我们可以随时取消?
不会。事实上,鉴于您希望能够停止该进程并重新启动,因此添加一个显式事务(或启用隐式事务)将是一个坏主意,因为在执行之前可能会停止该进程COMMIT
。在那种情况下,您将需要手动发出COMMIT
(如果您在SSMS中),或者如果您是通过SQL Agent作业运行的,则您将没有机会,并且最终可能会成为孤立事务。
另外,您可能需要设置@CHUNK_SIZE
较小的数字。锁升级通常发生在单个对象上获得5000个锁的情况下。根据行的大小以及是否执行行锁与页锁,您可能会超过该限制。如果一行的大小使得每个页面只能容纳1或2行,那么即使它正在执行页面锁定,也可能总是会遇到这种情况。
如果表已分区,则可以选择将表的LOCK_ESCALATION
选项(在SQL Server 2008中引入)设置为,AUTO
以便在升级时仅锁定分区,而不锁定整个表。或者,对于任何表,您都可以将相同的选项设置为DISABLE
,尽管您对此必须非常小心。有关详细信息,请参见ALTER TABLE。
以下是一些有关锁升级和阈值的文档:锁升级(它适用于“ SQL Server 2008 R2和更高版本”)。这是一篇有关检测和修复锁升级的博客文章:Microsoft SQL Server中的锁定(第12部分-锁升级)。
与确切的问题无关,但与问题中的查询有关,可以在此处进行一些改进(或者至少从查看的角度来看,似乎是这样):
对于您的循环,这样做WHILE (@@ROWCOUNT = @CHUNK_SIZE)
会稍微好一点,因为如果在上一次迭代中更新的行数少于请求UPDATE的数量,那么就没有工作要做。
如果该deleted
字段是一个BIT
数据类型,那么是不是该值被确定是否deletedDate
是2000-01-01
?为什么同时需要两者?
如果这两个字段是新字段,并且您按原样添加了它们,NULL
那么它可能是联机/非阻塞操作,并且现在想要将它们更新为“默认”值,那么就没有必要了。从SQL Server 2012(仅企业版)开始,添加NOT NULL
具有DEFAULT约束的列是非阻塞操作,只要DEFAULT的值是常数即可。因此,如果您尚未使用这些字段,只需NOT NULL
使用DEFAULT约束删除并重新添加。
如果在执行此UPDATE时没有其他进程在更新这些字段,则如果将要更新的记录排队,然后仅处理该队列,则速度会更快。当前方法会影响性能,因为您每次必须重新查询表以获取需要更改的集合。相反,您可以执行以下操作,该操作仅在这两个字段上扫描表一次,然后仅发出针对性很强的UPDATE语句。随时停止该进程并稍后再启动它也不会有任何损失,因为队列的初始填充只会找到剩下的要更新的记录。
- 创建一个临时表(#FullSet),该表中只包含来自聚集索引的键字段。
- 创建具有相同结构的第二个临时表(#CurrentSet)。
通过插入#FullSet SELECT TOP(n) KeyField1, KeyField2 FROM [huge-table] where deleted is null or deletedDate is null;
在TOP(n)
由于表的大小是在那里。由于表中有1亿行,您实际上并不需要用整个键集填充队列表,尤其是如果您计划如此频繁地停止该过程并稍后重新启动它时。因此,也许将其设置n
为100万,然后让其运行到完成。您始终可以在运行100万个(甚至更少)的SQL Agent作业中安排此时间,然后等待下一个安排的时间再次开始。然后,您可以安排每20分钟运行一次,以便在的各组之间有一定的强制呼吸空间n
,但仍会无人值守地完成整个过程。然后,当无事可做时,只需删除作业即可:-)。
- 循环执行:
- 通过类似的方法填充当前批次
DELETE TOP (4995) FROM #FullSet OUTPUT Deleted.KeyField INTO #CurrentSet (KeyField);
IF (@@ROWCOUNT = 0) BREAK;
- 使用类似的方法进行更新:
UPDATE ht SET ht.deleted = 0, ht.deletedDate='2000-01-01' FROM [huge-table] ht INNER JOIN #CurrentSet cs ON cs.KeyField = ht.KeyField;
- 清除当前集:
TRUNCATE TABLE #CurrentSet;
- 在某些情况下,添加过滤索引有助于将索引
SELECT
馈入#FullSet
临时表。以下是与添加此类索引有关的一些注意事项:
- WHERE条件应与查询的WHERE条件匹配,因此
WHERE deleted is null or deletedDate is null
- 在该过程的开始,大多数行都将符合您的WHERE条件,因此索引并不是那么有用。您可能要等到50%左右的某个位置才能添加。当然,由于几个因素的不同,它的帮助程度和最佳添加索引的时间也有所不同,因此这是反复试验的结果。
- 您可能必须手动更新STATS和/或重建索引以使其保持最新,因为基础数据的更改非常频繁
- 请务必记住,索引在帮助的同时
SELECT
会伤害,UPDATE
因为索引是该操作期间必须更新的另一个对象,因此会有更多的I / O。这既可以使用过滤索引(由于与过滤器匹配的行越少,更新的索引就会缩小),又需要等待一会儿才能添加索引(如果一开始它并没有多大帮助,那么就没有理由额外的I / O)。
更新:请参阅我对与上述问题有关的问题的回答,以完整地实现上面建议的内容,包括跟踪状态并彻底取消的机制:sql server:以小块的形式更新大表上的字段:如何获取进度/状态?