-
Non dovresti aggiornare 10.000 righe in un set a meno che tu non sia certo che l'operazione stia ottenendo i blocchi di pagina (a causa del fatto che più righe per pagina fanno parte di
UPDATE
operazione). Il problema è che l'escalation dei blocchi (dai blocchi di riga o di pagina a tabella) si verifica a 5000 blocchi . Quindi è più sicuro mantenerlo appena al di sotto di 5000, nel caso in cui l'operazione utilizzi Row Locks. -
Non dovresti non utilizzare SET ROWCOUNT per limitare il numero di righe che verranno modificate. Ci sono due problemi qui:
-
È stato deprecato dal rilascio di SQL Server 2005 (11 anni fa):
L'uso di SET ROWCOUNT non influirà sulle istruzioni DELETE, INSERT e UPDATE in una versione futura di SQL ServerSQL Server. Evita di utilizzare SET ROWCOUNT con le istruzioni DELETE, INSERT e UPDATE nel nuovo lavoro di sviluppo e pianifica di modificare le applicazioni che attualmente lo utilizzano. Per un comportamento simile, usa la sintassi TOP
-
Può influenzare più della semplice dichiarazione con cui hai a che fare:
L'impostazione dell'opzione SET ROWCOUNT determina l'interruzione dell'elaborazione della maggior parte delle istruzioni Transact-SQL quando sono state interessate dal numero di righe specificato. Questo include i trigger. L'opzione ROWCOUNT non influisce sui cursori dinamici, ma limita il set di righe di keyset e cursori insensibili. Questa opzione dovrebbe essere utilizzata con cautela.
Invece, usa il
TOP ()
clausola. -
-
Non c'è alcuno scopo nell'avere una transazione esplicita qui. Complica il codice e non hai alcuna gestione per un
ROLLBACK
, che non è nemmeno necessario poiché ogni istruzione è la sua transazione (ad es. auto-commit). -
Supponendo che trovi un motivo per mantenere la transazione esplicita, non hai un
TRY
/CATCH
struttura. Si prega di vedere la mia risposta su DBA.StackExchange per unTRY
/CATCH
modello che gestisce le transazioni:Siamo tenuti a gestire la transazione nel codice C# e nella procedura del negozio
Sospetto che il vero WHERE
La clausola non viene mostrata nel codice di esempio nella domanda, quindi basandosi semplicemente su ciò che è stato mostrato, un meglio il modello sarebbe:
DECLARE @Rows INT,
@BatchSize INT; -- keep below 5000 to be safe
SET @BatchSize = 2000;
SET @Rows = @BatchSize; -- initialize just to enter the loop
BEGIN TRY
WHILE (@Rows = @BatchSize)
BEGIN
UPDATE TOP (@BatchSize) tab
SET tab.Value = 'abc1'
FROM TableName tab
WHERE tab.Parameter1 = 'abc'
AND tab.Parameter2 = 123
AND tab.Value <> 'abc1' COLLATE Latin1_General_100_BIN2;
-- Use a binary Collation (ending in _BIN2, not _BIN) to make sure
-- that you don't skip differences that compare the same due to
-- insensitivity of case, accent, etc, or linguistic equivalence.
SET @Rows = @@ROWCOUNT;
END;
END TRY
BEGIN CATCH
RAISERROR(stuff);
RETURN;
END CATCH;
Testando @Rows
contro @BatchSize
, puoi evitare quell'UPDATE
finale query (nella maggior parte dei casi) perché il set finale è in genere un numero di righe inferiore a @BatchSize
, nel qual caso sappiamo che non ci sono più da elaborare (che è ciò che vedi nell'output mostrato nella tua risposta). Solo nei casi in cui l'insieme finale di righe è uguale a @BatchSize
questo codice eseguirà un UPDATE
finale interessando 0 righe.
Ho anche aggiunto una condizione a WHERE
clausola per evitare che le righe già aggiornate vengano aggiornate nuovamente.
NOTA SUL PRESTITO
Ho enfatizzato "meglio" sopra (come in "questo è un migliore model") perché questo ha diversi miglioramenti rispetto al codice originale dell'OP e funziona bene in molti casi, ma non è perfetto per tutti i casi. Per tabelle di almeno una certa dimensione (che varia a causa di diversi fattori, quindi posso' Per essere più specifici), le prestazioni diminuiranno poiché ci sono meno righe da correggere se:
- non esiste un indice per supportare la query, oppure
- c'è un indice, ma almeno una colonna in
WHERE
La clausola è un tipo di dati stringa che non utilizza regole di confronto binarie, quindi unCOLLATE
La clausola viene aggiunta alla query qui per forzare le regole di confronto binarie, e così facendo invalida l'indice (per questa particolare query).
Questa è la situazione incontrata da @mikesigs, che richiede quindi un approccio diverso. Il metodo aggiornato copia gli ID per tutte le righe da aggiornare in una tabella temporanea, quindi utilizza quella tabella temporanea per INNER JOIN
alla tabella in fase di aggiornamento sulle colonne della chiave dell'indice cluster. (È importante acquisire e partecipare all'indice cluster colonne, indipendentemente dal fatto che siano o meno le colonne della chiave primaria!).
Si prega di consultare la risposta di @mikesigs di seguito per i dettagli. L'approccio mostrato in quella risposta è un modello molto efficace che ho usato io stesso in molte occasioni. Le uniche modifiche che farei sono:
- Crea in modo esplicito il
#targetIds
tabella invece di usareSELECT INTO...
- Per il
#targetIds
tabella, dichiarare una chiave primaria raggruppata nelle colonne. - Per il
#batchIds
tabella, dichiarare una chiave primaria raggruppata nelle colonne. - Per l'inserimento in
#targetIds
, usaINSERT INTO #targetIds (column_name(s)) SELECT
e rimuovere ilORDER BY
perché non è necessario.
Quindi, se non hai un indice che può essere utilizzato per questa operazione e non puoi crearne uno temporaneamente che funzioni effettivamente (un indice filtrato potrebbe funzionare, a seconda del tuo WHERE
clausola per UPDATE
query), quindi prova l'approccio mostrato nella risposta di @mikesigs (e se usi quella soluzione, per favore votala).