Dal mio punto di vista il tuo server ha un serio problema di prestazioni. Anche se assumiamo che nessuno dei record nella query
select some_col with (nolock) where id_col between 57000000 and 57001000
era in memoria, non dovrebbero volerci 21 secondi per leggere le poche pagine in sequenza dal disco (il tuo indice cluster su id_col non dovrebbe essere frammentato se si tratta di un'identità automatica e non hai fatto qualcosa di stupido come aggiungere un "desc" alla definizione dell'indice).
Ma se non riesci/non vuoi risolverlo, il mio consiglio sarebbe quello di eseguire l'aggiornamento in piccoli pacchetti come 100-1000 record alla volta (a seconda di quanto tempo consuma la funzione di ricerca). Un aggiornamento/transazione non dovrebbe richiedere più di 30 secondi.
Ogni aggiornamento mantiene un blocco esclusivo su tutti i record che ha modificato fino al completamento della transazione. Se non utilizzi una transazione esplicita, ogni istruzione viene eseguita in un unico contesto di transazione automatica, quindi i blocchi vengono rilasciati al termine dell'istruzione di aggiornamento.
Ma puoi comunque imbatterti in deadlock in questo modo, a seconda di ciò che fanno gli altri processi. Se modificano anche più di un record alla volta, o anche se raccolgono e mantengono blocchi di lettura su più righe, puoi ottenere deadlock.
Per evitare i deadlock, la tua istruzione di aggiornamento deve bloccare tutti i record che modificherà contemporaneamente. Il modo per farlo è inserire la singola istruzione di aggiornamento (con solo le poche righe limitate da id_col) in una transazione serializzabile come
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
Per ogni aggiornamento questo richiederà un aggiornamento/blocco esclusivo dell'intervallo di chiavi sui record forniti (ma solo su di essi, perché limiti l'aggiornamento tramite la chiave dell'indice cluster). Attenderà il completamento di qualsiasi altro aggiornamento sugli stessi record, quindi otterrà il blocco (causando il blocco per tutte le altre transazioni, ma solo per i record specificati), quindi aggiornerà i record e rilascerà il blocco.
L'ultima istruzione extra è importante, perché richiederà un blocco dell'intervallo di chiavi fino a "infinito" e quindi impedirà anche gli inserimenti alla fine dell'intervallo durante l'esecuzione dell'istruzione di aggiornamento.