Sqlserver
 sql >> Database >  >> RDS >> Sqlserver

Come aggiornare una tabella di grandi dimensioni con milioni di righe in SQL Server?

  1. 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.

  2. Non dovresti non utilizzare SET ROWCOUNT per limitare il numero di righe che verranno modificate. Ci sono due problemi qui:

    1. È 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

    2. 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.

  3. 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).

  4. 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 un TRY / 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:

  1. non esiste un indice per supportare la query, oppure
  2. c'è un indice, ma almeno una colonna in WHERE La clausola è un tipo di dati stringa che non utilizza regole di confronto binarie, quindi un COLLATE 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:

  1. Crea in modo esplicito il #targetIds tabella invece di usare SELECT INTO...
  2. Per il #targetIds tabella, dichiarare una chiave primaria raggruppata nelle colonne.
  3. Per il #batchIds tabella, dichiarare una chiave primaria raggruppata nelle colonne.
  4. Per l'inserimento in #targetIds , usa INSERT INTO #targetIds (column_name(s)) SELECT e rimuovere il ORDER 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).