-
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. -
Du bør ikke bruge SET ROWCOUNT til at begrænse antallet af rækker, der vil blive ændret. Der er to problemer her:
-
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
-
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. -
-
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). -
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 enTRY
/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:
- der er intet indeks, der understøtter forespørgslen, eller
- der er et indeks, men mindst én kolonne i
WHERE
klausul er en strengdatatype, der ikke bruger en binær sortering, derfor enCOLLATE
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:
- Opret eksplicit
#targetIds
tabel i stedet for at brugeSELECT INTO...
- For
#targetIds
tabel, erklære en klynget primærnøgle på kolonnen(erne). - For
#batchIds
tabel, erklære en klynget primærnøgle på kolonnen(erne). - Til indsættelse i
#targetIds
, brugINSERT INTO #targetIds (column_name(s)) SELECT
og fjernORDER 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).