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

Hvordan opdaterer man en stor tabel med millioner af rækker i SQL Server?

  1. Du bør ikke opdatere 10.000 rækker i et sæt, medmindre du er sikker på, at operationen får sidelåse (på grund af at flere rækker pr. side er en del af UPDATE operation). Problemet er, at låseeskalering (fra enten række- eller side-til-bord-låse) sker ved 5000 låse . Så det er sikrest at holde det lige under 5000, bare hvis operationen bruger rækkelåse.

  2. Du bør ikke bruge SET ROWCOUNT til at begrænse antallet af rækker, der vil blive ændret. Der er to problemer her:

    1. Det er blevet forældet siden SQL Server 2005 blev udgivet (11 år siden):

      Brug af SET ROWCOUNT vil ikke påvirke DELETE-, INSERT- og UPDATE-sætninger i en fremtidig udgivelse af SQL Server. Undgå at bruge SET ROWCOUNT med DELETE-, INSERT- og UPDATE-sætninger i nyt udviklingsarbejde, og planlæg at ændre applikationer, der i øjeblikket bruger det. For en lignende adfærd skal du bruge TOP-syntaksen

    2. Det kan påvirke mere end blot det udsagn, du har med at gøre:

      Indstilling af indstillingen SET ROWCOUNT får de fleste Transact-SQL-sætninger til at stoppe behandlingen, når de er blevet påvirket af det angivne antal rækker. Dette inkluderer triggere. ROWCOUNT-indstillingen påvirker ikke dynamiske markører, men den begrænser rækkesættet af nøglesæt og ufølsomme markører. Denne mulighed skal bruges med forsigtighed.

    Brug i stedet TOP () klausul.

  3. Der er intet formål med at have en eksplicit transaktion her. Det komplicerer koden, og du har ingen håndtering af en ROLLBACK , hvilket ikke engang er nødvendigt, da hver erklæring er sin egen transaktion (dvs. auto-commit).

  4. Forudsat at du finder en grund til at beholde den eksplicitte transaktion, så har du ikke en TRY / CATCH struktur. Se venligst mit svar på DBA.StackExchange for en TRY / CATCH skabelon, der håndterer transaktioner:

    Er vi forpligtet til at håndtere transaktioner i C#-kode såvel som i butiksprocedure

Jeg formoder, at den rigtige WHERE klausulen vises ikke i eksempelkoden i spørgsmålet, så blot ved at stole på det, der er blevet vist, er en bedre model ville være:

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;

Ved at teste @Rows mod @BatchSize , kan du undgå den sidste UPDATE forespørgsel (i de fleste tilfælde), fordi det endelige sæt typisk er et eller andet antal rækker mindre end @BatchSize , i så fald ved vi, at der ikke er flere at behandle (hvilket er, hvad du ser i outputtet vist i dit svar). Kun i de tilfælde, hvor det endelige sæt rækker er lig med @BatchSize vil denne kode køre en sidste UPDATE påvirker 0 rækker.

Jeg har også tilføjet en betingelse til WHERE klausul for at forhindre rækker, der allerede er blevet opdateret, i at blive opdateret igen.

BEMÆRKNING VEDRØRENDE YDELSE

Jeg understregede "bedre" ovenfor (som i "dette er en bedre). model"), fordi dette har adskillige forbedringer i forhold til O.P.'s originale kode og fungerer fint i mange tilfælde, men er ikke perfekt til alle tilfælde. For tabeller af mindst en vis størrelse (som varierer på grund af flere faktorer, så jeg kan' ikke være mere specifik), vil ydeevnen forringes, da der er færre rækker at rette, hvis enten:

  1. der er intet indeks, der understøtter forespørgslen, eller
  2. der er et indeks, men mindst én kolonne i WHERE klausul er en strengdatatype, der ikke bruger en binær sortering, derfor en COLLATE klausul føjes til forespørgslen her for at fremtvinge den binære kollation, og hvis du gør det, ugyldiggøres indekset (for denne specifikke forespørgsel).

Dette er den situation, som @mikesigs stødte på, og derfor krævede en anden tilgang. Den opdaterede metode kopierer id'erne for alle rækker, der skal opdateres til en midlertidig tabel, og bruger derefter den midlertidige tabel til INNER JOIN til tabellen, der opdateres på den/de klyngede indeksnøglekolonne(r). (Det er vigtigt at fange og deltage i det klyngede indeks kolonner, uanset om det er de primære nøglekolonner eller ej!).

Se venligst @mikesigs svar nedenfor for detaljer. Den tilgang, der vises i det svar, er et meget effektivt mønster, som jeg selv har brugt ved mange lejligheder. De eneste ændringer, jeg ville foretage, er:

  1. Opret eksplicit #targetIds tabel i stedet for at bruge SELECT INTO...
  2. For #targetIds tabel, erklære en klynget primærnøgle på kolonnen(erne).
  3. For #batchIds tabel, erklære en klynget primærnøgle på kolonnen(erne).
  4. Til indsættelse i #targetIds , brug INSERT INTO #targetIds (column_name(s)) SELECT og fjern ORDER BY da det er unødvendigt.

Så hvis du ikke har et indeks, der kan bruges til denne handling, og ikke midlertidigt kan oprette et, der rent faktisk fungerer (et filtreret indeks kan muligvis fungere, afhængigt af din WHERE klausul for UPDATE forespørgsel), prøv derefter fremgangsmåden vist i @mikesigs svar (og hvis du bruger den løsning, så stem venligst op).



  1. Sådan eksporteres clob-feltdata i oracle sql-udvikler

  2. Sådan installeres og sikres MariaDB på CentOS 7

  3. Præstationsovervågning og -revision PostgreSQL - Topressourcer

  4. Hvordan opretter man forbindelse til mssql ved hjælp af pdo gennem PHP og Linux?