-
U mag geen 10.000 rijen in een set bijwerken, tenzij u zeker weet dat de bewerking paginavergrendelingen krijgt (omdat meerdere rijen per pagina deel uitmaken van de
UPDATE
operatie). Het probleem is dat vergrendelingsescalatie (van rij- of pagina- naar tabelvergrendelingen) optreedt bij 5000 vergrendelingen . Het is dus het veiligst om het net onder de 5000 te houden, voor het geval de operatie Row Locks gebruikt. -
Je moet niet gebruik SET ROWCOUNT om het aantal rijen dat wordt gewijzigd te beperken. Er zijn hier twee problemen:
-
Het is verouderd sinds SQL Server 2005 werd uitgebracht (11 jaar geleden):
Het gebruik van SET ROWCOUNT heeft geen invloed op de instructies DELETE, INSERT en UPDATE in een toekomstige versie van SQL Server. Vermijd het gebruik van SET ROWCOUNT met DELETE-, INSERT- en UPDATE-instructies in nieuw ontwikkelingswerk en plan om applicaties die het momenteel gebruiken, aan te passen. Gebruik voor soortgelijk gedrag de TOP-syntaxis
-
Het kan van invloed zijn op meer dan alleen de verklaring waarmee u te maken hebt:
Als u de optie SET ROWCOUNT instelt, worden de meeste Transact-SQL-instructies niet meer verwerkt wanneer ze zijn beïnvloed door het opgegeven aantal rijen. Dit is inclusief triggers. De optie ROWCOUNT heeft geen invloed op dynamische cursors, maar beperkt wel de rijen van keyset en ongevoelige cursors. Deze optie moet met de nodige voorzichtigheid worden gebruikt.
Gebruik in plaats daarvan de
TOP ()
clausule. -
-
Het heeft geen zin om hier een expliciete transactie te hebben. Het compliceert de code en je hebt geen behandeling voor een
ROLLBACK
, wat niet eens nodig is omdat elke verklaring zijn eigen transactie is (d.w.z. auto-commit). -
Ervan uitgaande dat u een reden vindt om de expliciete transactie te behouden, dan heeft u geen
TRY
/CATCH
structuur. Zie mijn antwoord op DBA.StackExchange voor eenTRY
/CATCH
sjabloon dat transacties afhandelt:Moeten we de transactie in C#-code en in de winkelprocedure afhandelen
Ik vermoed dat de echte WHERE
clausule wordt niet getoond in de voorbeeldcode in de vraag, dus gewoon vertrouwen op wat is getoond, een beter model zou zijn:
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;
Door @Rows
. te testen tegen @BatchSize
, kunt u die laatste UPDATE
. vermijden query (in de meeste gevallen) omdat de uiteindelijke set meestal een aantal rijen kleiner is dan @BatchSize
, in welk geval we weten dat er niet meer te verwerken is (wat je ziet in de output die in je antwoord wordt getoond). Alleen in die gevallen waarin de laatste reeks rijen gelijk is aan @BatchSize
zal deze code een laatste UPDATE
uitvoeren beïnvloedt 0 rijen.
Ik heb ook een voorwaarde toegevoegd aan de WHERE
clausule om te voorkomen dat rijen die al zijn bijgewerkt, opnieuw worden bijgewerkt.
OPMERKING BETREFFENDE PRESTATIES
Ik heb hierboven "beter" benadrukt (zoals in, "dit is een beter model") omdat dit verschillende verbeteringen heeft ten opzichte van de originele code van de OP, en in veel gevallen prima werkt, maar niet in alle gevallen perfect is. Voor tabellen van ten minste een bepaalde grootte (die varieert als gevolg van verschillende factoren, dus ik kan ' (om specifieker te zijn), zullen de prestaties verslechteren omdat er minder rijen zijn om op te lossen als:
- er is geen index om de zoekopdracht te ondersteunen, of
- er is een index, maar ten minste één kolom in de
WHERE
clausule is een string-gegevenstype dat geen binaire sortering gebruikt, vandaar eenCOLLATE
clausule wordt hier aan de zoekopdracht toegevoegd om de binaire sortering te forceren, en hierdoor wordt de index ongeldig (voor deze specifieke zoekopdracht).
Dit is de situatie die @mikesigs is tegengekomen en vraagt dus om een andere aanpak. De bijgewerkte methode kopieert de ID's voor alle rijen die moeten worden bijgewerkt naar een tijdelijke tabel en gebruikt die tijdelijke tabel vervolgens om INNER JOIN
naar de tabel die wordt bijgewerkt op de geclusterde indexsleutelkolom(men). (Het is belangrijk om vast te leggen en deel te nemen aan de geclusterde index kolommen, ongeacht of dit de primaire sleutelkolommen zijn!).
Zie het antwoord van @mikesigs hieronder voor meer informatie. De aanpak die in dat antwoord wordt getoond, is een zeer effectief patroon dat ik zelf vaak heb gebruikt. De enige wijzigingen die ik zou aanbrengen zijn:
- Maak expliciet de
#targetIds
tabel in plaats vanSELECT INTO...
- Voor de
#targetIds
tabel, declareer een geclusterde primaire sleutel op de kolom(men). - Voor de
#batchIds
tabel, declareer een geclusterde primaire sleutel op de kolom(men). - Voor invoegen in
#targetIds
, gebruikINSERT INTO #targetIds (column_name(s)) SELECT
en verwijder deORDER BY
omdat het niet nodig is.
Dus als u geen index heeft die voor deze bewerking kan worden gebruikt, en er tijdelijk geen kunt maken die echt werkt (een gefilterde index kan werken, afhankelijk van uw WHERE
clausule voor de UPDATE
query), probeer dan de benadering die wordt getoond in het antwoord van @mikesigs (en als je die oplossing gebruikt, stem er dan op).