sql >> Database >  >> RDS >> Sqlserver

Hoe een grote tabel met miljoenen rijen in SQL Server bij te werken?

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

  2. Je moet niet gebruik SET ROWCOUNT om het aantal rijen dat wordt gewijzigd te beperken. Er zijn hier twee problemen:

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

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

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

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

  1. er is geen index om de zoekopdracht te ondersteunen, of
  2. er is een index, maar ten minste één kolom in de WHERE clausule is een string-gegevenstype dat geen binaire sortering gebruikt, vandaar een COLLATE 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:

  1. Maak expliciet de #targetIds tabel in plaats van SELECT INTO...
  2. Voor de #targetIds tabel, declareer een geclusterde primaire sleutel op de kolom(men).
  3. Voor de #batchIds tabel, declareer een geclusterde primaire sleutel op de kolom(men).
  4. Voor invoegen in #targetIds , gebruik INSERT INTO #targetIds (column_name(s)) SELECT en verwijder de ORDER 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).



  1. Voor- en nadelen van het gebruik van SqlCommand Prepare in C#?

  2. Wat is er nieuw met MySQL-replicatie in MySQL 8.0

  3. Hoe jsonb-arrays op te vragen met de IN-operator

  4. TIMESTAMP() Voorbeelden – MySQL