UPDATE语句会更新符合过滤条件的数据行,SQL Server为了保证数据的完整性,在UPDATE语句的执行过程中,会给语句中所涉及的表和数据加上适当的更新锁或排他锁(关于锁的详细信息,后续章节会详细介绍)。由于更新锁/排他锁相对于共享锁的锁兼容性较低,因此当UPDATE语句存在性能问题时,将对SELECT操作造成阻塞。所以需要注意,编写UPDATE语句时,对于小数据量的更新,笔者会建议使用主键或唯一键字段来过滤需要更新的数据。
在默认事务级别中,UPDATE语句进行查询时会添加更新锁(U锁),若找到了需要更新的数据,就会将更新锁转换(Convert)为排他锁(X锁)。由于更新锁和排他锁的兼容性弱,在需要较长执行时间的更新语句或事务中,容易造成阻塞。为了避免造成大量阻塞,应尽量保证更新语句的效率,适当建立索引。
笔者经常会被问道:“A表被更新了,写错过滤条件,有办法恢复吗?”或者“B表被更新了,忘了写过滤条件,有办法恢复吗?”其实,有许多方法可以避免这类问题,例如后面会讲到的OUTPUT子句,以及在更新数据前显式设置一个事务,等等。
在进行数据维护时,经常会遇到一些查询或更新频率非常高的表数据被更新或新字段初始化,对于7×24小时运作的数据库环境来说,长时间的更新维护通常会造成表被锁,从而影响业务功能,无法正常运作,不得不停机操作。笔者常用的一个方法是预先计算好结果,然后分批更新。分批更新时,还需要注意一些小细节。例如,对于比较复杂的计算,预先计算好结果,然后再更新物理表,这样就可避免在更新物理表时再进行计算,从而影响更新时间。下面提供一段示例代码:
--创建临时表,保存需要更新的数据主键
SELECT TOP(0) sod.SalesOrderID, sod.SalesOrderDetailID, sod.ProductID, sod.UnitPrice INTO #SalesOrderNeedUpdate FROM Sales.SalesOrderDetail AS sod;
--创建聚集,唯一索引,以使后续的删除操作尽量快速
ALTER TABLE #SalesOrderNeedUpdate ADD CONSTRAINT PK_#SalesOrderNeedUpdate PRIMARY KEY(SalesOrderDetailID,SalesOrderID);
--初始化临时表,将需要更新的数据的主键初始化到临时表中。本示例中,需要更新所有的数据
INSERT INTO #SalesOrderNeedUpdate ( SalesOrderID, SalesOrderDetailID, ProductID ) SELECT sod.SalesOrderID, sod.SalesOrderDetailID, sod.ProductID FROM Sales.SalesOrderDetail AS sod;
--初始化需要更新的字段
UPDATE sonu SET UnitPrice=p.ListPrice FROM #SalesOrderNeedUpdate AS sonu JOIN Production.Product AS p ON sonu.ProductID = p.ProductID;
--创建临时表,保存需要更新的当前小集合
SELECT TOP(0) sod.SalesOrderID, sod.SalesOrderDetailID, sod.UnitPrice INTO #SalesOrderPagedData FROM Sales.SalesOrderDetail AS sod;
--开始分批更新数据
WHILE EXISTS(SELECT 1 FROM #SalesOrderNeedUpdate AS sonu) BEGIN INSERT INTO #SalesOrderPagedData ( SalesOrderID, SalesOrderDetailID, UnitPrice ) SELECT TOP(3000) sonu.SalesOrderID, sonu.SalesOrderDetailID, sonu.UnitPrice FROM #SalesOrderNeedUpdate AS sonu; UPDATE sod SET sod.UnitPrice=godp.UnitPrice FROM Sales.SalesOrderDetail AS sod JOIN #SalesOrderPagedData AS sopd ON sod.SalesOrderDetailID = sopd.SalesOrderDetailID AND sod.SalesOrderID = sopd.SalesOrderID; DELETE sonu FROM #SalesOrderNeedUpdate AS sonu JOIN #SalesOrderPagedData AS sopd ON sonu.SalesOrderDetailID = sopd.SalesOrderDetailID AND sonu.SalesOrderID = sopd.SalesOrderID; END