从我的角度来看,您的服务器存在严重的性能问题。即使我们假设查询中没有任何记录
select some_col with (nolock) where id_col between 57000000 and 57001000
在内存中,从磁盘顺序读取几页不应该花费 21 秒(如果它是一个自动标识并且你没有做一些愚蠢的事情,比如添加一个“desc”,那么你在 id_col 上的聚集索引不应该是碎片化的到索引定义)。
但是如果你不能/不会解决这个问题,我的建议是一次以小包进行更新,比如 100-1000 条记录(取决于查找函数消耗的时间)。一项更新/事务不应超过 30 秒。
您会看到每次更新都会对其修改的所有记录保持独占锁定,直到事务完成。如果您不使用显式事务,则每个语句都在单个自动事务上下文中执行,因此当更新语句完成时锁会被释放。
但是您仍然可能会以这种方式遇到死锁,具体取决于其他进程的操作。如果他们一次修改了多个记录,或者即使他们收集并持有多行的读锁,您也可能会出现死锁。
为避免死锁,您的更新语句需要锁定它一次将修改的所有记录。这样做的方法是将单个更新语句(只有几行受 id_col 限制)放在可序列化的事务中,例如
IF @@TRANCOUNT > 0
-- Error: You are in a transaction context already
SET NOCOUNT ON
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
-- Insert Loop here to work "x" through the id range
BEGIN TRANSACTION
UPDATE SOMETABLE
SET [some_col] = dbo.ufn_SomeFunction(CONVERT(NVARCHAR(500), another_column))
WHERE [some_col] = 243 AND id_col BETWEEN x AND x+500 -- or whatever keeps the update in the small timerange
COMMIT
-- Next loop
-- Get all new records while you where running the loop. If these are too many you may have to paginate this also:
BEGIN TRANSACTION
UPDATE SOMETABLE
SET [some_col] = dbo.ufn_SomeFunction(CONVERT(NVARCHAR(500), another_column))
WHERE [some_col] = 243 AND id_col >= x
COMMIT
对于每次更新,这将对给定记录进行更新/排他键范围锁定(但仅限于它们,因为您通过聚集索引键限制更新)。它将等待同一记录上的任何其他更新完成,然后获取它的锁(导致所有其他事务阻塞,但仍仅针对给定记录),然后更新记录并释放锁。
最后一个额外的语句很重要,因为它会将键范围锁定到“无穷大”,从而在更新语句运行时甚至阻止在范围末尾的插入。