sql >> Database >  >> RDS >> Database

Gegevenswijzigingen onder Read Committed Snapshot Isolation

[ Zie de index voor de hele serie ]

Het vorige bericht in deze serie liet zien hoe een T-SQL-instructie wordt uitgevoerd onder read-committ snapshot-isolatie (RCSI ) ziet normaal gesproken een momentopname van de vastgelegde status van de database zoals deze was toen de uitvoering van de instructie begon. Dat is een goede beschrijving van hoe dingen werken voor uitspraken die gegevens lezen, maar er zijn belangrijke verschillen voor instructies die onder RCSI worden uitgevoerd en die bestaande rijen wijzigen .

Ik benadruk de aanpassing van bestaande rijen hierboven, omdat de volgende overwegingen alleen van toepassing zijn op UPDATE en DELETE bewerkingen (en de bijbehorende acties van een MERGE uitspraak). Voor alle duidelijkheid:INSERT uitspraken zijn specifiek uitgesloten van het gedrag dat ik ga beschrijven omdat invoegingen bestaande . niet wijzigen gegevens.

Vergrendelingen en rijversies bijwerken

Het eerste verschil is dat update- en delete-instructies rijversies niet lezen onder RCSI bij het zoeken naar de te wijzigen bronrijen. Update en verwijder instructies onder RCSI in plaats daarvan verkrijg update locks bij het zoeken naar kwalificerende rijen. Het gebruik van updatevergrendelingen zorgt ervoor dat de zoekbewerking rijen vindt om te wijzigen met behulp van de meest recente vastgelegde gegevens .

Zonder updatevergrendelingen zou de zoekopdracht gebaseerd zijn op een mogelijk verouderde versie van de dataset (toegewezen data zoals het was toen de datamodificatie-instructie begon). Dit herinnert je misschien aan het triggervoorbeeld dat we de vorige keer zagen, waar een READCOMMITTEDLOCK hint werd gebruikt om van RCSI terug te keren naar de vergrendelingsimplementatie van read commit isolation. Die hint was in dat voorbeeld nodig om een ​​belangrijke actie niet te baseren op verouderde informatie. Hier wordt dezelfde redenering gebruikt. Een verschil is dat de READCOMMITTEDLOCK hint verwerft gedeelde vergrendelingen in plaats van updatevergrendelingen. Bovendien verwerft SQL Server automatisch updatevergrendelingen om gegevenswijzigingen onder RCSI te beschermen zonder dat we een expliciete hint hoeven toe te voegen.

Het nemen van updatevergrendelingen zorgt er ook voor dat de update- of delete-instructie blokkeert als het een incompatibele vergrendeling tegenkomt, bijvoorbeeld een exclusieve vergrendeling die een gegevenswijziging tijdens de vlucht beschermt die is uitgevoerd door een andere gelijktijdige transactie.

Een extra complicatie is dat het aangepaste gedrag alleen van toepassing is naar de tabel die het doel is van de update- of wisbewerking. Andere tafels in de dezelfde verwijder of update verklaring, inclusief aanvullende verwijzingen naar de doeltabel, blijf rijversies gebruiken .

Er zijn waarschijnlijk enkele voorbeelden nodig om dit verwarrende gedrag wat duidelijker te maken...

Testopstelling

Het volgende script zorgt ervoor dat we allemaal zijn ingesteld om RCSI te gebruiken, maakt een eenvoudige tabel en voegt er twee voorbeeldrijen aan toe:

ALTER DATABASE Sandpit
SET READ_COMMITTED_SNAPSHOT ON
WITH ROLLBACK IMMEDIATE;
GO
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
GO
CREATE TABLE dbo.Test
(
    RowID integer PRIMARY KEY,
    Data integer NOT NULL
);
GO
INSERT dbo.Test
    (RowID, Data)
VALUES 
    (1, 1234),
    (2, 2345);

De volgende stap moet worden uitgevoerd in een aparte sessie . Het start een transactie en verwijdert beide rijen uit de testtabel (lijkt vreemd, maar dit zal binnenkort allemaal logisch worden):

BEGIN TRANSACTION;
DELETE dbo.Test 
WHERE RowID IN (1, 2);

Merk op dat de transactie opzettelijk opengelaten . Hierdoor blijven exclusieve vergrendelingen op beide rijen verwijderd (samen met de gebruikelijke intentie-exclusieve vergrendelingen op de bevattende pagina en de tabel zelf), zoals de onderstaande query kan gebruiken om te laten zien:

SELECT
    resource_type,
    resource_description,
    resource_associated_entity_id,
    request_mode,
    request_status
FROM sys.dm_tran_locks
WHERE 
    request_session_id = @@SPID;

De selectietest

Terugschakelen naar de oorspronkelijke sessie , is het eerste dat ik wil laten zien dat reguliere select-statements die RCSI gebruiken, nog steeds zien dat de twee rijen worden verwijderd. De onderstaande selectiequery gebruikt rijversies om de laatste vastgelegde gegevens te retourneren op het moment dat de instructie begint:

SELECT *
FROM dbo.Test;

In het geval dat dat verrassend lijkt, onthoud dat het weergeven van de rijen als verwijderd zou betekenen dat een niet-vastgelegde weergave van de gegevens wordt weergegeven, wat niet is toegestaan ​​bij read-commit-isolatie.

De verwijdertest

Ondanks het succes van de geselecteerde test, een poging om verwijderen dezelfde rijen van de huidige sessie worden geblokkeerd. Je zou je kunnen voorstellen dat deze blokkering optreedt wanneer de operatie exclusief . probeert te verwerven sloten, maar dat is niet het geval.

Het verwijderen maakt geen gebruik van rijversiebeheer om de rijen te lokaliseren die u wilt verwijderen; het probeert in plaats daarvan updatevergrendelingen te verkrijgen. Updatevergrendelingen zijn niet compatibel met de exclusieve rijvergrendelingen van de sessie met de open transactie, dus de query blokkeert:

DELETE dbo.Test 
WHERE RowID IN (1, 2);

Het geschatte zoekplan voor deze instructie laat zien dat de te verwijderen rijen worden geïdentificeerd door een reguliere zoekbewerking voordat een afzonderlijke operator de daadwerkelijke verwijdering uitvoert:

We kunnen de vergrendelingen zien die in dit stadium worden vastgehouden door dezelfde vergrendelingsquery uit te voeren als voorheen (vanuit een andere sessie) en eraan te denken de SPID-referentie te wijzigen in die van de geblokkeerde query. De resultaten zien er als volgt uit:

Onze verwijderquery is geblokkeerd bij de Clustered Index Seek-operator, die wacht op een updatevergrendeling om lezen te krijgen gegevens. Dit toont aan dat het lokaliseren van de te verwijderen rijen onder RCSI updatevergrendelingen verwerft in plaats van potentieel verouderde versiegegevens te lezen. Het laat ook zien dat de blokkering niet te wijten is aan het verwijdergedeelte van de bewerking dat wacht op het verkrijgen van een exclusieve vergrendeling.

De updatetest

Annuleer de geblokkeerde zoekopdracht en probeer in plaats daarvan de volgende update:

UPDATE dbo.Test
SET Data = Data + 1000
WHERE RowID IN (1, 2);

Het geschatte uitvoeringsplan is vergelijkbaar met het plan dat te zien is in de verwijdertest:

De Compute Scalar is er om het resultaat te bepalen van het toevoegen van 1000 aan de huidige waarde van de gegevenskolom in elke rij, die wordt gelezen door de Clustered Index Seek. Deze verklaring zal ook blokkeren wanneer uitgevoerd, vanwege de updatevergrendeling die is aangevraagd door de leesbewerking. De onderstaande schermafbeelding toont de vergrendelingen die worden vastgehouden wanneer de query blokkeert:

Net als voorheen wordt de zoekopdracht geblokkeerd bij het zoeken, wachtend tot de incompatibele exclusieve vergrendeling wordt vrijgegeven, zodat een updatevergrendeling kan worden verkregen.

De invoegtest

De volgende test bevat een instructie die een nieuwe rij in onze testtabel invoegt, met behulp van de gegevenskolomwaarde van de bestaande rij met ID 1 in de tabel. Bedenk dat deze rij nog steeds exclusief is vergrendeld door sessie met de open transactie:

INSERT dbo.Test
    (RowID, Data)
SELECT 3, Data
FROM dbo.Test
WHERE RowID = 1;

Het uitvoeringsplan is weer vergelijkbaar met de vorige tests:

Deze keer is de zoekopdracht niet geblokkeerd . Dit toont aan dat de updatevergrendelingen niet werden verkregen bij het lezen gegevens voor het invoegen. Deze query gebruikte in plaats daarvan rijversiebeheer om de gegevenskolomwaarde voor de nieuw ingevoegde rij te verkrijgen. Updatevergrendelingen zijn niet verkregen omdat deze instructie geen rijen heeft gevonden om aan te passen , het leest alleen gegevens om in de insert te gebruiken.

We kunnen deze nieuwe rij in de tabel zien met de selectietestquery van eerder:

Merk op dat we zijn in staat om de nieuwe rij bij te werken en te verwijderen (waarvoor updatevergrendelingen vereist zijn) omdat er geen conflicterende exclusieve vergrendeling is. De sessie met de open transactie heeft alleen exclusieve sloten op rij 1 en 2:

-- Update the new row
UPDATE dbo.Test
SET Data = 9999
WHERE RowID = 3;
-- Show the data
SELECT * FROM dbo.Test;
-- Delete the new row
DELETE dbo.Test
WHERE RowID = 3;

Deze test bevestigt dat insert statements geen update locks krijgen bij het lezen , omdat ze, in tegenstelling tot updates en verwijderingen, niet wijzigen een bestaande rij. Het leesgedeelte van een insert instructie gebruikt het normale RCSI rijversiebeheer.

Test met meerdere referenties

Ik heb eerder vermeld dat alleen de enkele tabelverwijzing die wordt gebruikt om rijen te lokaliseren die moeten worden gewijzigd, updatevergrendelingen krijgt; andere tabellen in dezelfde update- of delete-instructie lezen nog steeds rijversies. Als een speciaal geval van dat algemene principe, een verklaring voor gegevenswijziging met meerdere verwijzingen naar de dezelfde tabel past alleen updatevergrendelingen toe op de ene instantie gebruikt om rijen te vinden die u wilt wijzigen. Deze laatste test illustreert dit meer complexe gedrag stap voor stap.

Het eerste dat we nodig hebben, is een nieuwe derde rij voor onze testtabel, deze keer met een nul in de kolom Gegevens:

INSERT dbo.Test
    (RowID, Data)
VALUES
    (3, 0);

Zoals verwacht gaat deze invoeging verder zonder te blokkeren, wat resulteert in een tabel die er als volgt uitziet:

Onthoud dat de tweede sessie nog steeds exclusief is vergrendelt op dit punt op rijen 1 en 2. We zijn vrij om sloten op rij 3 te kopen als dat nodig is. De volgende query is degene die we zullen gebruiken om het gedrag te tonen met meerdere verwijzingen naar de doeltabel:

-- Multi-reference update test
UPDATE WriteRef
SET Data = ReadRef.Data * 2
OUTPUT 
    ReadRef.RowID, 
    ReadRef.Data,
    INSERTED.RowID AS UpdatedRowID,
    INSERTED.Data AS NewDataValue
FROM dbo.Test AS ReadRef
JOIN dbo.Test AS WriteRef
    ON WriteRef.RowID = ReadRef.RowID + 2
WHERE 
    ReadRef.RowID = 1;

Dit is een complexere query, maar de werking ervan is relatief eenvoudig. Er zijn twee verwijzingen naar de testtabel, de ene heb ik gealiast als ReadRef en de andere als WriteRef. Het idee is om lezen van rij 1 (met behulp van een rijversie) via ReadRef, en naar bijwerken de derde rij (die een updatevergrendeling nodig heeft) met WriteRef.

De query specificeert rij 1 expliciet in de waar-clausule voor de leestabelverwijzing. Het voegt zich bij de schrijfreferentie naar de dezelfde tabel door 2 aan die RowID toe te voegen (dus rij 3 identificeren). De update-instructie gebruikt ook een uitvoerclausule om een ​​resultatenset te retourneren met de waarden die zijn gelezen uit de brontabel en de resulterende wijzigingen die zijn aangebracht in rij 3.

Het geschatte zoekplan voor deze verklaring is als volgt:

De eigenschappen van de zoekopdracht met het label (1) laat zien dat deze zoekopdracht op de ReadRef . staat alias, gegevens lezen uit de rij met RowID 1:

Deze zoekbewerking vindt geen rij die wordt bijgewerkt, dus updatevergrendelingen zijn niet genomen; het lezen wordt uitgevoerd met behulp van versiegegevens. Het lezen wordt niet geblokkeerd door de exclusieve vergrendelingen van de andere sessie.

De rekenschaal met het label (2) definieert een expressie met het label 1004 die de bijgewerkte gegevenskolomwaarde berekent. Expressie 1009 berekent de rij-ID die moet worden bijgewerkt (1 + 2 =rij-ID 3):

De tweede zoekactie is een verwijzing naar dezelfde tabel (3). Hiermee wordt de rij gelokaliseerd die zal worden bijgewerkt (rij 3) met expressie 1009:

Omdat deze zoekopdracht een rij lokaliseert die moet worden gewijzigd, wordt een updatevergrendeling wordt genomen in plaats van rijversies te gebruiken. Er is geen conflicterende exclusieve vergrendeling op rij-ID 3, dus het vergrendelingsverzoek wordt onmiddellijk ingewilligd.

De laatste gemarkeerde operator (4) is de update-operatie zelf. De updatevergrendeling op rij 3 is geüpgraded naar een exclusieve lock op dit punt, net voordat de wijziging daadwerkelijk wordt uitgevoerd. Deze operator retourneert ook de gegevens die zijn gespecificeerd in de output-clausule van de update-verklaring:

Het resultaat van de update-instructie (gegenereerd door de output-clausule) wordt hieronder getoond:

De uiteindelijke staat van de tabel is zoals hieronder weergegeven:

We kunnen de vergrendelingen bevestigen die tijdens de uitvoering zijn genomen met behulp van een Profiler-tracering:

Dit toont aan dat slechts een enkele update rijtoetsvergrendeling is verkregen. Wanneer deze rij de update-operator bereikt, wordt het slot geconverteerd naar een exclusief slot. Aan het einde van de verklaring wordt het slot vrijgegeven.

Mogelijk kunt u aan de trace-uitvoer zien dat de lock-hash-waarde voor de update-locked rij (98ec012aa510) is in mijn testdatabase. De volgende query laat zien dat deze lock-hash inderdaad is gekoppeld aan RowID 3 in de geclusterde index:

SELECT RowID, %%LockRes%%
FROM dbo.Test;

Houd er rekening mee dat de updatevergrendelingen in deze voorbeelden een kortere levensduur hebben dan de updatevergrendelingen die worden gebruikt als we een UPDLOCK specificeren hint. Deze interne updatevergrendelingen worden vrijgegeven aan het einde van de instructie, terwijl UPDLOCK sloten worden vastgehouden tot het einde van de transactie.

Dit is het einde van de demonstratie van gevallen waarin RCSI updatevergrendelingen verwerft om huidige vastgelegde gegevens te lezen in plaats van rijversiebeheer te gebruiken.

Gedeelde en sleutelbereiksloten onder RCSI

Er zijn een aantal andere scenario's waarin de database-engine nog steeds vergrendelingen kan verkrijgen onder RCSI. Deze situaties hebben allemaal te maken met de noodzaak om de juistheid te behouden die zou worden bedreigd door te vertrouwen op mogelijk verouderde versiegegevens.

Gedeelde sloten gebruikt voor validatie van buitenlandse sleutels

Voor twee tabellen in een directe externe-sleutelrelatie moet de database-engine stappen ondernemen om ervoor te zorgen dat de beperkingen niet worden geschonden door te vertrouwen op mogelijk verouderde versies van leesbewerkingen. De huidige implementatie doet dit door over te schakelen naar locking read commit bij het openen van gegevens als onderdeel van een automatische externe sleutelcontrole.

Door gedeelde vergrendelingen te gebruiken, zorgt de integriteitscontrole ervoor dat de allerlaatste vastgelegde gegevens (geen oude versie) of blokkeringen worden gelezen als gevolg van een gelijktijdige wijziging tijdens de vlucht. De omschakeling naar locking read commit is alleen van toepassing op de specifieke toegangsmethode die wordt gebruikt om externe sleutelgegevens te controleren; andere gegevenstoegang in dezelfde instructie blijft rijversies gebruiken.

Dit gedrag is alleen van toepassing op instructies die gegevens wijzigen, waarbij de wijziging rechtstreeks van invloed is op een externe-sleutelrelatie. Voor wijzigingen aan de (bovenliggende) tabel waarnaar wordt verwezen, betekent dit updates die van invloed zijn op de waarde waarnaar wordt verwezen (tenzij deze is ingesteld op NULL ) en alle verwijderingen. Voor de verwijzende (onderliggende) tabel betekent dit alle invoegingen en updates (nogmaals, tenzij de sleutelreferentie NULL is ). Dezelfde overwegingen zijn van toepassing op de componenteffecten van een MERGE .

Hieronder ziet u een voorbeeld van een uitvoeringsplan met een zoekopdracht voor een externe sleutel waarbij gedeelde sloten nodig zijn:

Serializeerbaar voor trapsgewijze externe sleutels

Waar de externe-sleutelrelatie een trapsgewijze actie heeft, vereist correctheid een lokale escalatie naar serialiseerbare isolatiesemantiek. Dit betekent dat u sleutelbereikvergrendelingen ziet worden genomen voor een trapsgewijze referentiële actie. Zoals het geval was voor de updatevergrendelingen die eerder werden gezien, zijn deze sleutelbereikvergrendelingen gericht op de instructie, niet op de transactie. Een voorbeelduitvoeringsplan dat laat zien waar de interne serialiseerbare sloten onder RCSI worden genomen, wordt hieronder weergegeven:

Andere scenario's

Er zijn veel andere specifieke gevallen waarin de motor automatisch de levensduur van sloten verlengt of lokaal het isolatieniveau verhoogt om de juistheid te garanderen. Deze omvatten de serialiseerbare semantiek die wordt gebruikt bij het onderhouden van een gerelateerde geïndexeerde weergave, of bij het onderhouden van een index met de IGNORE_DUP_KEY opties ingesteld.

De afhaalboodschap is dat RCSI de hoeveelheid vergrendeling vermindert, maar deze niet altijd volledig kan elimineren.

Volgende keer

Het volgende bericht in deze serie gaat over het isolatieniveau van snapshots.

[ Zie de index voor de hele serie ]


  1. MySQL IN voorwaarde limiet

  2. Hoe WordPress te installeren:de serversoftware

  3. Het minimaliseren van de impact van het verbreden van een IDENTITEIT-kolom - deel 2

  4. Java Oracle-uitzondering - maximum aantal expressies in een lijst is 1000