Analysera och förhindra dödlägen i Azure SQL Database

Gäller för:Azure SQL Database

Den här artikeln visar hur du identifierar deadlock-problem i Azure SQL Database, använder deadlock-grafer och Query Store för att identifiera frågorna i ett deadlock och planerar och testar ändringar för att förhindra att deadlock-problemen uppstår igen.

Den här artikeln fokuserar på att identifiera och analysera dödlägen på grund av låskonkurring. Läs mer om andra typer av dödlägen i resurser som kan blockeras.

Så här uppstår dödlägen i Azure SQL Database

Varje ny databas i Azure SQL Database har inställningen för skrivskyddade ögonblicksbilder (RCSI) aktiverad som standard. Blockering mellan sessioner som läser data och sessioner som skriver data minimeras under RCSI, som använder radversioner för att öka samtidigheten. Blockering och dödlägen kan dock fortfarande inträffa i databaser i Azure SQL Database eftersom:

  • Frågor som ändrar data kan blockera varandra.
  • Frågor kan köras under isoleringsnivåer som ökar blockeringen. Isoleringsnivåer kan anges via klientbiblioteksmetoder, frågetips eller SET-instruktioner i Transact-SQL.
  • RCSI kan inaktiveras, vilket gör att databasen använder delade (S) lås för att skydda SELECT-instruktioner som körs under den läsincheckade isoleringsnivån. Detta kan öka blockering och dödlägen.

Ett exempel på ett dödläge

Ett dödläge uppstår när två eller flera aktiviteter permanent blockerar varandra eftersom varje aktivitet har ett lås på en resurs som den andra aktiviteten försöker låsa. Ett dödläge kallas också för ett cykliskt beroende: vid ett dödläge med två aktiviteter har transaktion A ett beroende av transaktion B och transaktion B stänger cirkeln genom att ha ett beroende av transaktion A.

Till exempel:

  1. Session A påbörjar en explicit transaktion och kör en uppdateringsinstrukitet som hämtar ett uppdateringslås (U) på en rad i tabellen SalesLT.Product som konverteras till ett exklusivt (X) lås.
  2. Session B kör en uppdateringsuttryck som ändrar SalesLT.ProductDescription tabellen. Uppdateringssatsen ansluter till SalesLT.Product tabellen för att hitta rätt rader att uppdatera.
    • Session B hämtar ett uppdateringslås (U) på 72 rader i SalesLT.ProductDescription tabellen.
    • Session B behöver ett delat lås på rader i tabellen SalesLT.Product, inklusive raden som är låst av session A. Session B blockeras på SalesLT.Product.
  3. Session A fortsätter sin transaktion och kör nu en uppdatering mot SalesLT.ProductDescription tabellen. Session A blockeras av session B på SalesLT.ProductDescription.

A diagram showing two sessions in a deadlock. Each session owns a resource that the other process needs in order to continue.

Alla transaktioner i ett dödläge kommer att vänta på obestämd tid om inte en av de deltagande transaktionerna rullas tillbaka, till exempel på grund av att dess session avslutades.

Databasmotorns dödlägesövervakare kontrollerar regelbundet efter uppgifter som är i ett dödläge. Om dödlägesövervakaren identifierar ett cykliskt beroende väljer den en av uppgifterna som offer och avslutar sin transaktion med fel 1205: "Transaktion (process-ID N) låstes på låsresurser med en annan process och har valts som dödlägesoffer. Kör transaktionen igen." Om du bryter dödläget på det här sättet kan den andra aktiviteten eller aktiviteterna i dödläget slutföra sina transaktioner.

Kommentar

Läs mer om kriterierna för att välja ett dödlägesoffer i avsnittet Deadlock process list i den här artikeln.

Overview of a deadlock between two sessions. One session has been chosen as the deadlock victim.

Programmet med transaktionen som valts som dödlägesoffer bör försöka transaktionen igen, som vanligtvis slutförs efter att den andra transaktionen eller transaktionerna som är inblandade i dödläget har slutförts.

Det är bästa praxis att införa en kort, slumpmässig fördröjning innan du försöker igen för att undvika att stöta på samma dödläge igen. Läs mer om hur du utformar omprövningslogik för tillfälliga fel.

Standardisoleringsnivå i Azure SQL Database

Nya databaser i Azure SQL Database aktiverar skrivskyddade ögonblicksbilder (RCSI) som standard. RCSI ändrar beteendet för den läsincheckade isoleringsnivån för att använda radversionshantering för att ge konsekvens på instruktionsnivå utan att använda delade (S) lås för SELECT-instruktioner.

Med RCSI aktiverat:

  • Instruktioner som läser data blockerar inte instruktioner som ändrar data.
  • Instruktioner som ändrar data blockerar inte instruktioner som läser data.

Isoleringsnivån för ögonblicksbilder är också aktiverad som standard för nya databaser i Azure SQL Database. Isolering av ögonblicksbilder är ytterligare en radbaserad isoleringsnivå som ger konsekvens på transaktionsnivå för data och som använder radversioner för att välja rader som ska uppdateras. Om du vill använda ögonblicksbildisolering måste frågor eller anslutningar uttryckligen ange transaktionsisoleringsnivån till SNAPSHOT. Detta kan bara göras när ögonblicksbildisolering är aktiverat för databasen.

Du kan identifiera om RCSI och/eller ögonblicksbildisolering är aktiverade med Transact-SQL. Anslut till databasen i Azure SQL Database och kör följande fråga:

SELECT name, is_read_committed_snapshot_on, snapshot_isolation_state_desc
FROM sys.databases
WHERE name = DB_NAME();
GO

Om RCSI är aktiverat is_read_committed_snapshot_on returnerar kolumnen värdet 1. Om ögonblicksbildisolering är aktiverat snapshot_isolation_state_desc returnerar kolumnen värdet .

Om RCSI har inaktiverats för en databas i Azure SQL Database undersöker du varför RCSI inaktiverades innan den aktiverades igen. Programkoden kan ha skrivits och förväntade sig att frågor som läser data blockeras av frågor som skriver data, vilket resulterar i felaktiga resultat från konkurrensförhållanden när RCSI är aktiverat.

Tolka dödlägeshändelser

En dödlägeshändelse genereras efter att deadlockhanteraren i Azure SQL Database identifierar ett dödläge och väljer en transaktion som offer. Om du med andra ord konfigurerar aviseringar för dödlägen utlöses meddelandet efter att ett enskilt dödläge har lösts. Det finns ingen användaråtgärd som behöver vidtas för det här dödläget. Program ska skrivas för att inkludera omprövningslogik så att de automatiskt fortsätter efter att ha fått fel 1205: "Transaktion (process-ID N) låstes på låsresurser med en annan process och har valts som dödlägesoffer. Kör transaktionen igen."

Det är dock användbart att konfigurera aviseringar eftersom dödlägen kan uppstå igen. Med deadlockaviseringar kan du undersöka om ett mönster av upprepade dödlägen inträffar i databasen, i vilket fall du kan välja att vidta åtgärder för att förhindra att dödlägen upprepas. Läs mer om aviseringar i avsnittet Övervaka och varna om dödlägen i den här artikeln.

De vanligaste metoderna för att förhindra dödlägen

Den lägsta riskmetoden för att förhindra att dödlägen upprepas är i allmänhet att justera icke-illustrerade index för att optimera frågor som ingår i dödläget.

  • Risken är låg för den här metoden eftersom justering av icke-illustrerade index inte kräver ändringar i själva frågekoden, vilket minskar risken för ett användarfel vid omskrivning av Transact-SQL som gör att felaktiga data returneras till användaren.
  • Effektiv icke-illustrerad indexjustering hjälper frågor att hitta data för att läsa och ändra mer effektivt. Genom att minska mängden data som en fråga behöver komma åt minskar sannolikheten för blockering och dödlägen kan ofta förhindras.

I vissa fall kan skapande eller justering av ett grupperat index minska blockering och dödlägen. Eftersom det klustrade indexet ingår i alla icke-grupperade indexdefinitioner kan det vara en I/O-intensiv och tidskrävande åtgärd att skapa eller ändra ett klustrade index i större tabeller med befintliga index som inte är grupperade. Läs mer om riktlinjer för design av klustrade index.

När indexjusteringen inte lyckas med att förhindra dödlägen är andra metoder tillgängliga:

  • Om dödläget bara inträffar när en viss plan väljs för en av frågorna som ingår i dödläget kan det förhindra att dödlägen upprepas om du tvingar fram en frågeplan med Query Store.
  • Om du skriver om Transact-SQL för en eller flera transaktioner som ingår i dödläget kan du också förhindra dödlägen. Att dela upp explicita transaktioner i mindre transaktioner kräver noggrann kodning och testning för att säkerställa data giltighet när samtidiga ändringar inträffar.

Läs mer om var och en av dessa metoder i avsnittet Förhindra ett dödläge från att upprepas i den här artikeln.

Övervaka och varna för dödlägen

I den här artikeln använder vi exempeldatabasen AdventureWorksLT för att konfigurera aviseringar för dödlägen, orsaka ett exempel på ett dödläge, analysera dödlägesdiagrammet för exempellåset och testa ändringar för att förhindra att dödläget upprepas.

Vi använder SQL Server Management Studio-klienten (SSMS) i den här artikeln eftersom den innehåller funktioner för att visa dödlägesdiagram i ett interaktivt visuellt läge. Du kan använda andra klienter, till exempel Azure Data Studio , för att följa exemplen, men du kanske bara kan visa dödlägesdiagram som XML.

Skapa AdventureWorksLT-databasen

Om du vill följa med i exemplen skapar du en ny databas i Azure SQL Database och väljer Exempeldata som datakälla.

Detaljerade anvisningar om hur du skapar AdventureWorksLT med Azure-portalen, Azure CLI eller PowerShell finns i Snabbstart: Skapa en enkel Azure SQL Database-databas.

Konfigurera dödlägesaviseringar i Azure-portalen

Om du vill konfigurera aviseringar för dödlägeshändelser följer du stegen i artikeln Skapa aviseringar för Azure SQL Database och Azure Synapse Analytics med azure-portalen.

Välj Deadlocks (Dödlägen ) som signalnamn för aviseringen. Konfigurera åtgärdsgruppen så att den meddelar dig med valfri metod, till exempel åtgärdstypen E-post/SMS/Push/Röst.

Samla in dödlägesdiagram i Azure SQL Database med utökade händelser

Deadlock-grafer är en omfattande informationskälla om de processer och lås som ingår i ett dödläge. Samla in deadlock-grafer med Extended Events (XEvents) i Azure SQL Database genom att sqlserver.database_xml_deadlock_report samla in händelsen.

Du kan samla in dödlägesdiagram med XEvents med antingen ringbuffertmålet eller ett händelsefilmål. Överväganden för att välja lämplig måltyp sammanfattas i följande tabell:

Metod Förmåner Överväganden Användningsscenarier
Ringbuffertmål
  • Enkel installation med endast Transact-SQL.
  • Händelsedata rensas när XEvents-sessionen stoppas av någon anledning, till exempel att ta databasen offline eller en databasredundans.
  • Databasresurser används för att underhålla data i ringbufferten och för att fråga sessionsdata.
  • Samla in exempelspårningsdata för testning och inlärning.
  • Skapa för kortsiktiga behov om du inte kan konfigurera en session med hjälp av ett händelsefilmål omedelbart.
  • Använd som en "landningsplatta" för spårningsdata när du har konfigurerat en automatiserad process för att spara spårningsdata i en tabell.
Mål för händelsefil
  • Bevarar händelsedata till en blob i Azure Storage så att data är tillgängliga även efter att sessionen har stoppats.
  • Händelsefiler kan laddas ned från Azure-portalen eller Azure Storage Explorer och analyseras lokalt, vilket inte kräver att databasresurser används för att köra frågor mot sessionsdata.
  • Installationen är mer komplex och kräver konfiguration av en Azure Storage-container och databasomfattande autentiseringsuppgifter.
  • Allmän användning när du vill att händelsedata ska sparas även efter att händelsesessionen har avslutats.
  • Du vill köra en spårning som genererar större mängder händelsedata än du vill spara i minnet.

Välj den måltyp som du vill använda:

Ringbuffertmålet är bekvämt och enkelt att konfigurera, men har en begränsad kapacitet, vilket kan leda till att äldre händelser går förlorade. Ringbufferten bevarar inte händelser till lagringen och ringbuffertmålet rensas när XEvents-sessionen stoppas. Det innebär att alla XEvents som samlas in inte är tillgängliga när databasmotorn startas om av någon anledning, till exempel en redundansväxling. Ringbuffertmålet passar bäst för inlärning och kortsiktiga behov om du inte har möjlighet att konfigurera en XEvents-session till ett händelsefilmål omedelbart.

Den här exempelkoden skapar en XEvents-session som samlar in dödlägesdiagram i minnet med hjälp av ringbuffertmålet. Det maximala minne som tillåts för ringbuffertmålet är 4 MB och sessionen körs automatiskt när databasen är online, till exempel efter en redundansväxling.

Om du vill skapa och sedan starta en XEvents-session för sqlserver.database_xml_deadlock_report händelsen som skriver till ringbuffertmålet ansluter du till databasen och kör följande Transact-SQL:

CREATE EVENT SESSION [deadlocks] ON DATABASE 
ADD EVENT sqlserver.database_xml_deadlock_report
ADD TARGET package0.ring_buffer 
WITH (STARTUP_STATE=ON, MAX_MEMORY=4 MB)
GO

ALTER EVENT SESSION [deadlocks] ON DATABASE
    STATE = START;
GO

Orsaka ett dödläge i AdventureWorksLT

Kommentar

Det här exemplet fungerar i AdventureWorksLT databasen med standardschemat och data när RCSI har aktiverats. Se Skapa AdventureWorksLT-databasen för instruktioner för att skapa databasen.

För att orsaka ett dödläge måste du ansluta två sessioner till AdventureWorksLT databasen. Vi refererar till dessa sessioner som Session A och Session B.

I Session A kör du följande Transact-SQL. Den här koden påbörjar en explicit transaktion och kör en enda instruktion som uppdaterar SalesLT.Product tabellen. För att göra detta hämtar transaktionen ett uppdateringslås (U) på en rad i tabellen SalesLT.Product som konverteras till ett exklusivt (X) lås. Vi lämnar transaktionen öppen.

BEGIN TRAN

    UPDATE SalesLT.Product SET SellEndDate = SellEndDate + 1
        WHERE Color = 'Red';

Kör nu följande Transact-SQL i Session B. Den här koden påbörjar inte uttryckligen en transaktion. I stället fungerar den i transaktionsläge för automatisk återtagande. Den här instruktionen SalesLT.ProductDescription uppdaterar tabellen. Uppdateringen tar ut ett uppdateringslås (U) på 72 rader i SalesLT.ProductDescription tabellen. Frågan kopplas till andra tabeller, inklusive SalesLT.Product tabellen.

UPDATE SalesLT.ProductDescription SET Description = Description
    FROM SalesLT.ProductDescription as pd
    JOIN SalesLT.ProductModelProductDescription as pmpd on
        pd.ProductDescriptionID = pmpd.ProductDescriptionID
    JOIN SalesLT.ProductModel as pm on
        pmpd.ProductModelID = pm.ProductModelID
    JOIN SalesLT.Product as p on
        pm.ProductModelID=p.ProductModelID
    WHERE p.Color = 'Silver';

För att slutföra den här uppdateringen behöver session B ett delat (S) lås på rader i tabellen SalesLT.Product, inklusive raden som är låst av session A. Session B kommer att blockeras på SalesLT.Product.

Återgå till session A. Kör följande Transact-SQL-instruktion. Detta kör en andra UPDATE-instruktion som en del av den öppna transaktionen.

    UPDATE SalesLT.ProductDescription SET Description = Description
        FROM SalesLT.ProductDescription as pd
        JOIN SalesLT.ProductModelProductDescription as pmpd on
            pd.ProductDescriptionID = pmpd.ProductDescriptionID
        JOIN SalesLT.ProductModel as pm on
            pmpd.ProductModelID = pm.ProductModelID
        JOIN SalesLT.Product as p on
            pm.ProductModelID=p.ProductModelID
        WHERE p.Color = 'Red';

Den andra uppdateringssatsen i Session A blockeras av session BSalesLT.ProductDescription.

Session A och session B blockerar nu varandra ömsesidigt. Ingen av transaktionerna kan fortsätta eftersom var och en behöver en resurs som är låst av den andra.

Efter några sekunder identifierar dödlägesövervakaren att transaktionerna i session A och session B blockerar varandra ömsesidigt och att ingen av dem kan göra framsteg. Du bör se ett dödläge med session A vald som ett dödlägesoffer. Ett felmeddelande visas i Session A med text som liknar följande:

Msg 1205, Level 13, State 51, Line 7 Transaction (Process ID 91) var låst på låsresurser med en annan process och har valts som dödlägesoffer. Kör transaktionen igen.

Session B slutförs.

Om du konfigurerar dödlägesaviseringar i Azure-portalen bör du få ett meddelande kort efter att dödläget har inträffat.

Visa grafer för dödlägen från en XEvents-session

Om du har konfigurerat en XEvents-session för att samla in dödlägen och ett dödläge har inträffat efter att sessionen startades kan du visa en interaktiv grafisk visning av dödlägesdiagrammet samt XML för dödlägesdiagrammet.

Det finns olika metoder för att hämta information om dödläge för mål- och händelsefilmålen för ringbufferten. Välj det mål som du använde för XEvents-sessionen:

Om du konfigurerar en XEvents-session som skriver till ringbufferten kan du köra frågor mot dödlägesinformation med följande Transact-SQL. Innan du kör frågan ersätter du värdet @tracename för med namnet på xEvents-sessionen.

DECLARE @tracename sysname = N'deadlocks';

WITH ring_buffer AS (
    SELECT CAST(target_data AS XML) as rb
    FROM sys.dm_xe_database_sessions AS s 
    JOIN sys.dm_xe_database_session_targets AS t 
        ON CAST(t.event_session_address AS BINARY(8)) = CAST(s.address AS BINARY(8))
    WHERE s.name = @tracename and
    t.target_name = N'ring_buffer'
), dx AS (
    SELECT 
        dxdr.evtdata.query('.') as deadlock_xml_deadlock_report
    FROM ring_buffer
    CROSS APPLY rb.nodes('/RingBufferTarget/event[@name=''database_xml_deadlock_report'']') AS dxdr(evtdata)
) 
SELECT 
    d.query('/event/data[@name=''deadlock_cycle_id'']/value').value('(/value)[1]', 'int') AS [deadlock_cycle_id],
    d.value('(/event/@timestamp)[1]', 'DateTime2') AS [deadlock_timestamp],
    d.query('/event/data[@name=''database_name'']/value').value('(/value)[1]', 'nvarchar(256)') AS [database_name],
    d.query('/event/data[@name=''xml_report'']/value/deadlock') AS deadlock_xml,
    LTRIM(RTRIM(REPLACE(REPLACE(d.value('.', 'nvarchar(2000)'),CHAR(10),' '),CHAR(13),' '))) as query_text
FROM dx
CROSS APPLY deadlock_xml_deadlock_report.nodes('(/event/data/value/deadlock/process-list/process/inputbuf)') AS ib(d)
ORDER BY [deadlock_timestamp] DESC;
GO

Visa och spara ett dödlägesdiagram i XML

Om du visar ett dödlägesdiagram i XML-format kan du kopiera inputbuffer Transact-SQL-uttrycken som ingår i dödläget. Du kanske också föredrar att analysera dödlägen i ett textbaserat format.

Om du har använt en Transact-SQL-fråga för att returnera information om ett dödlägesdiagram väljer du värdet i deadlock_xml kolumnen från valfri rad för att öppna xml-koden för dödläget i ett nytt fönster i SSMS.

XML-koden för det här exemplets dödlägesdiagram är:

<deadlock>
  <victim-list>
    <victimProcess id="process24756e75088" />
  </victim-list>
  <process-list>
    <process id="process24756e75088" taskpriority="0" logused="6528" waitresource="KEY: 8:72057594045202432 (98ec012aa510)" waittime="192" ownerId="1011123" transactionname="user_transaction" lasttranstarted="2022-03-08T15:44:43.490" XDES="0x2475c980428" lockMode="U" schedulerid="3" kpid="30192" status="suspended" spid="89" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2022-03-08T15:44:49.250" lastbatchcompleted="2022-03-08T15:44:49.210" lastattention="1900-01-01T00:00:00.210" clientapp="Microsoft SQL Server Management Studio - Query" hostname="LAPTOP-CHRISQ" hostpid="16716" loginname="chrisqpublic" isolationlevel="read committed (2)" xactid="1011123" currentdb="8" currentdbname="AdventureWorksLT" lockTimeout="4294967295" clientoption1="671096864" clientoption2="128056">
      <executionStack>
        <frame procname="unknown" queryhash="0xef52b103e8b9b8ca" queryplanhash="0x02b0f58d7730f798" line="1" stmtstart="2" stmtend="792" sqlhandle="0x02000000c58b8f1e24e8f104a930776e21254b1771f92a520000000000000000000000000000000000000000">
unknown    </frame>
      </executionStack>
      <inputbuf>
    UPDATE SalesLT.ProductDescription SET Description = Description
        FROM SalesLT.ProductDescription as pd
        JOIN SalesLT.ProductModelProductDescription as pmpd on
            pd.ProductDescriptionID = pmpd.ProductDescriptionID
        JOIN SalesLT.ProductModel as pm on
            pmpd.ProductModelID = pm.ProductModelID
        JOIN SalesLT.Product as p on
            pm.ProductModelID=p.ProductModelID
        WHERE p.Color = 'Red'   </inputbuf>
    </process>
    <process id="process2476d07d088" taskpriority="0" logused="11360" waitresource="KEY: 8:72057594045267968 (39e18040972e)" waittime="2641" ownerId="1013536" transactionname="UPDATE" lasttranstarted="2022-03-08T15:44:46.807" XDES="0x2475ca80428" lockMode="S" schedulerid="2" kpid="94040" status="suspended" spid="95" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2022-03-08T15:44:46.807" lastbatchcompleted="2022-03-08T15:44:46.760" lastattention="1900-01-01T00:00:00.760" clientapp="Microsoft SQL Server Management Studio - Query" hostname="LAPTOP-CHRISQ" hostpid="16716" loginname="chrisqpublic" isolationlevel="read committed (2)" xactid="1013536" currentdb="8" currentdbname="AdventureWorksLT" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
      <executionStack>
        <frame procname="unknown" queryhash="0xef52b103e8b9b8ca" queryplanhash="0x02b0f58d7730f798" line="1" stmtstart="2" stmtend="798" sqlhandle="0x020000002c85bb06327c0852c0be840fc1e30efce2b7c8090000000000000000000000000000000000000000">
unknown    </frame>
      </executionStack>
      <inputbuf>
    UPDATE SalesLT.ProductDescription SET Description = Description
        FROM SalesLT.ProductDescription as pd
        JOIN SalesLT.ProductModelProductDescription as pmpd on
            pd.ProductDescriptionID = pmpd.ProductDescriptionID
        JOIN SalesLT.ProductModel as pm on
            pmpd.ProductModelID = pm.ProductModelID
        JOIN SalesLT.Product as p on
            pm.ProductModelID=p.ProductModelID
        WHERE p.Color = 'Silver';   </inputbuf>
    </process>
  </process-list>
  <resource-list>
    <keylock hobtid="72057594045202432" dbid="8" objectname="9e011567-2446-4213-9617-bad2624ccc30.SalesLT.ProductDescription" indexname="PK_ProductDescription_ProductDescriptionID" id="lock2474df12080" mode="U" associatedObjectId="72057594045202432">
      <owner-list>
        <owner id="process2476d07d088" mode="U" />
      </owner-list>
      <waiter-list>
        <waiter id="process24756e75088" mode="U" requestType="wait" />
      </waiter-list>
    </keylock>
    <keylock hobtid="72057594045267968" dbid="8" objectname="9e011567-2446-4213-9617-bad2624ccc30.SalesLT.Product" indexname="PK_Product_ProductID" id="lock2474b588580" mode="X" associatedObjectId="72057594045267968">
      <owner-list>
        <owner id="process24756e75088" mode="X" />
      </owner-list>
      <waiter-list>
        <waiter id="process2476d07d088" mode="S" requestType="wait" />
      </waiter-list>
    </keylock>
  </resource-list>
</deadlock>

Så här sparar du dödlägesdiagrammet som en XML-fil:

  1. Välj Arkiv och Spara som....
  2. Lämna värdet Spara som typ som standard-XML-filer (*.xml)
  3. Ange det filnamn som du vill använda.
  4. Välj Spara.

Spara ett dödlägesdiagram som en XDL-fil som kan visas interaktivt i SSMS

Det kan vara användbart att visa en interaktiv representation av ett dödlägesdiagram för att få en snabb översikt över de processer och resurser som ingår i ett dödläge och snabbt identifiera det låsta offret.

Så här sparar du ett dödlägesdiagram som en fil som kan visas grafiskt av SSMS:

  1. Välj värdet i deadlock_xml kolumnen från valfri rad för att öppna dödlägesdiagrammets XML i ett nytt fönster i SSMS.

  2. Välj Arkiv och Spara som....

  3. Ange Spara som typ till Alla filer.

  4. Ange filnamnet till önskat namn, med tillägget inställt på .xdl.

  5. Välj Spara.

    A screenshot in SSMS of saving a deadlock graph XML file to a file with the xsd extension.

  6. Stäng filen genom att välja X på fliken överst i fönstret eller genom att välja Arkiv och sedan Stänga.

  7. Öppna filen igen i SSMS genom att välja Arkiv, sedan Öppna och sedan Fil. Välj den fil som du sparade med .xdl tillägget.

    Dödlägesdiagrammet visas nu i SSMS med en visuell representation av de processer och resurser som ingår i dödläget.

    Screenshot of an xdl file opened in SSMS. The deadlock graph is displayed graphically, with processes indicated by ovals and lock resources as rectangles.

Analysera ett dödläge för Azure SQL Database

Ett dödlägesdiagram har vanligtvis tre noder:

  • Offerlista. Processidentifierare för dödlägesoffer.
  • Processlista. Information om alla processer som ingår i dödläget. Deadlock-grafer använder termen "process" för att representera en session som kör en transaktion.
  • Resurslista. Information om resurserna som ingår i dödläget.

När du analyserar ett dödläge är det användbart att gå igenom dessa noder.

Lista över offer för dödläge

Listan över offer för dödläge visar den process som valdes som ett dödlägesoffer. I den visuella representationen av ett dödlägesdiagram representeras processerna av ovaler. Dödläget offer processen har en "X" dras över ovalen.

Screenshot of the visual display of a deadlock. The oval representing the process selected as victim has an X drawn across it.

I XML-vyn för ett dödlägesdiagramvictim-list ger noden ett ID för den process som utsattes för dödläget.

I vårt exempel är ID:t för offerprocessen process24756e75088. Vi kan använda det här ID:t när vi undersöker noderna för processlistan och resurslistan för att lära oss mer om offerprocessen och de resurser som den låste eller begärde att låsa.

Lista över dödlägesprocesser

Listan över dödlägesprocesser är en omfattande informationskälla om transaktionerna som ingår i dödläget.

Den grafiska representationen av dödlägesdiagrammet visar endast en delmängd av informationen som finns i XML:en för dödlägesdiagrammet. Ovalerna i dödlägesdiagrammet representerar processen och visar information, inklusive:

  • Serverprocess-ID, även kallat sessions-ID eller SPID.

  • Prioritet för dödläge för sessionen. Om två sessioner har olika prioriteter för dödlägen väljs sessionen med den lägre prioriteten som dödlägesoffer. I det här exemplet har båda sessionerna samma prioritet för dödläge.

  • Mängden transaktionslogg som används av sessionen i byte. Om båda sessionerna har samma prioritet för dödläget väljer dödlägesövervakaren den session som är billigare att återställa som ett dödlägesoffer. Kostnaden bestäms genom att jämföra antalet loggbyte som skrivits till den punkten i varje transaktion.

    I vårt exempel hade dödläget session_id 89 använt en lägre mängd transaktionslogg och valts som dödlägesoffer.

Dessutom kan du visa indatabufferten för den sista instruktionskörningen i varje session före dödläget genom att hovra musen över varje process. Indatabufferten visas i en knappbeskrivning.

Screenshot of a deadlock graph displayed visually in SSMS. Two ovals represent processes. The inputbuff for one process is shown.

Ytterligare information är tillgänglig för processer i XML-vyn för dödlägesdiagrammet, inklusive:

  • Identifiera information för sessionen, till exempel klientnamn, värdnamn och inloggningsnamn.
  • Frågeplanens hash för den sista instruktionen som kördes av varje session före dödläget. Frågeplanens hash är användbar för att hämta mer information om frågan från Query Store.

I vårt exempel dödläge:

  • Vi kan se att båda sessionerna kördes med SSMS-klienten under chrisqpublic-inloggningen.
  • Frågeplanens hash för den senaste instruktionen som kördes före dödläget av vårt dödlägesoffer är 0x02b0f58d7730f798. Vi kan se texten i den här instruktionen i indatabufferten.
  • Frågeplanens hash för den senaste instruktionen som kördes av den andra sessionen i vårt dödläge är också 0x02b0f58d7730f798. Vi kan se texten i den här instruktionen i indatabufferten. I det här fallet har båda frågorna samma hash för frågeplanen eftersom frågorna är identiska, förutom ett literalvärde som används som ett likhetspredikat.

Vi använder dessa värden senare i den här artikeln för att hitta ytterligare information i Query Store.

Begränsningar för indatabufferten i listan över dödlägesprocesser

Det finns vissa begränsningar att vara medveten om när det gäller information om indatabuffert i listan över dödlägesprocesser.

Frågetexten kan trunkeras i indatabufferten. Indatabufferten är begränsad till de första 4 000 tecknen i instruktionen som körs.

Dessutom kanske vissa instruktioner som ingår i dödläget inte ingår i dödlägesdiagrammet. I vårt exempel körde Session A två uppdateringsinstruktioner i en enda transaktion. Endast den andra uppdateringssatsen, uppdateringen som orsakade dödläget, ingår i grafen för dödläge. Den första uppdateringssatsen som kördes av Session A spelade en roll i dödläget genom att blockera session B. Indatabufferten, query_hashoch relaterad information för den första instruktionen som körs av session A ingår inte i dödlägesdiagrammet.

För att identifiera den fullständiga Transact-SQL-körningen i en transaktion med flera instruktioner som ingår i ett dödläge måste du antingen hitta relevant information i den lagrade proceduren eller programkoden som körde frågan eller köra en spårning med extended events för att samla in fullständiga instruktioner som körs av sessioner som är inblandade i ett dödläge när det inträffar. Om en instruktion som ingår i dödläget har trunkerats och endast delvis Transact-SQL visas i indatabufferten kan du hitta Transact-SQL för -instruktionen i Query Store med körningsplanen.

Lista över låsta resurser

Listan över låsta resurser visar vilka låsresurser som ägs och väntar på av processerna i dödläget.

Resurser representeras av rektanglar i den visuella representationen av dödläget:

Screenshot of a deadlock graph, displayed visually in SSMS. Rectangles show the resources that are involved in the deadlock.

Kommentar

Du kanske märker att databasnamn representeras som unikadientifers i deadlock-grafer för databaser i Azure SQL Database. Det här är physical_database_name för databasen som anges i sys.databases och sys.dm_user_db_resource_governance dynamiska hanteringsvyer.

I det här exemplet dödläge:

  • Dödläget offer, som vi har kallat session A:

    • Äger ett exklusivt (X) lås på en nyckel i indexet PK_Product_ProductID i SalesLT.Product tabellen.
    • Begär ett uppdateringslås (U) på en nyckel i indexet PK_ProductDescription_ProductDescriptionID i SalesLT.ProductDescription tabellen.
  • Den andra processen, som vi har kallat session B:

    • Äger ett uppdateringslås (U) på en nyckel i indexet PK_ProductDescription_ProductDescriptionID i SalesLT.ProductDescription tabellen.
    • Begär ett delat (S) lås på en nyckel i indexet PK_ProductDescription_ProductDescriptionID i SalesLT.ProductDescription tabellen.

Vi kan se samma information i XML för dödlägesdiagrammet i resurslistans nod.

Hitta frågekörningsplaner i Query Store

Det är ofta användbart att undersöka frågekörningsplanerna för instruktioner som ingår i dödläget. Dessa körningsplaner finns ofta i Query Store med hjälp av frågeplanens hash från XML-vyn i blockeringsdiagrammets processlista.

Den här Transact-SQL-frågan letar efter frågeplaner som matchar frågeplanens hash som vi hittade för vårt exempellås. Anslut till användardatabasen i Azure SQL Database för att köra frågan.

DECLARE @query_plan_hash binary(8) = 0x02b0f58d7730f798

SELECT 
    qrsi.end_time as interval_end_time,
    qs.query_id,
    qp.plan_id,
    qt.query_sql_text, 
    TRY_CAST(qp.query_plan as XML) as query_plan,
    qrs.count_executions
FROM sys.query_store_query as qs
JOIN sys.query_store_query_text as qt on qs.query_text_id=qt.query_text_id
JOIN sys.query_store_plan as qp on qs.query_id=qp.query_id
JOIN sys.query_store_runtime_stats qrs on qp.plan_id = qrs.plan_id
JOIN sys.query_store_runtime_stats_interval qrsi on qrs.runtime_stats_interval_id=qrsi.runtime_stats_interval_id
WHERE query_plan_hash =  @query_plan_hash
ORDER BY interval_end_time, query_id;
GO

Du kanske inte kan hämta en frågekörningsplan från Query Store, beroende på dina Query Store-CLEANUP_POLICY eller QUERY_CAPTURE_MODE inställningar. I det här fallet kan du ofta få nödvändig information genom att visa den uppskattade körningsplanen för frågan.

Leta efter mönster som ökar blockeringen

När du undersöker frågekörningsplaner som ingår i dödlägen bör du se upp för mönster som kan bidra till blockering och dödlägen.

  • Tabell- eller indexgenomsökningar. När frågor som ändrar data körs under RCSI görs valet av rader som ska uppdateras med hjälp av en blockerande genomsökning där ett uppdateringslås (U) tas på dataraden när datavärden läses. Om dataraden inte uppfyller uppdateringsvillkoren släpps uppdateringslåset och nästa rad låses och genomsöks.

    Om du justerar index för att hjälpa till att ändra frågor kan du hitta rader mer effektivt, vilket minskar antalet utfärdade uppdateringslås. Detta minskar risken för blockering och dödlägen.

  • Indexerade vyer som refererar till mer än en tabell. När du ändrar en tabell som refereras till i en indexerad vy måste databasmotorn också underhålla den indexerade vyn. Detta kräver fler lås och kan leda till ökad blockering och dödlägen. Indexerade vyer kan också orsaka att uppdateringsåtgärder körs internt under den lässkyddade isoleringsnivån.

  • Ändringar av kolumner som refereras i begränsningar för sekundärnyckel. När du ändrar kolumner i en tabell som refereras till i en FOREIGN KEY-begränsning måste databasmotorn leta efter relaterade rader i referenstabellen. Det går inte att använda radversioner för dessa läsningar. I de fall där sammanhängande uppdateringar eller borttagningar är aktiverade kan isoleringsnivån eskaleras till serialiserbar under instruktionens varaktighet för att skydda mot fantominfogningar.

  • Lås tips. Leta efter tabelltips som anger isoleringsnivåer som kräver fler lås. Dessa tips omfattar HOLDLOCK (som motsvarar serialiserbar), SERIALIZABLE, READCOMMITTEDLOCK (som inaktiverar RCSI) och REPEATABLEREAD. Dessutom kan tips som PAGLOCK, TABLOCK, UPDLOCKoch XLOCK öka risken för blockering och dödlägen.

    Om dessa tips finns på plats undersöker du varför tipsen implementerades. Dessa tips kan förhindra konkurrensförhållanden och säkerställa data giltighet. Det kan vara möjligt att lämna dessa tips på plats och förhindra framtida dödlägen med hjälp av en alternativ metod i avsnittet Förhindra ett dödläge från att upprepas i den här artikeln om det behövs.

    Kommentar

    Läs mer om beteende när du ändrar data med hjälp av radversionshantering i versionsguiden för transaktionslåsning och radversion.

När du undersöker den fullständiga koden för en transaktion, antingen i en körningsplan eller i programfrågekoden, letar du efter ytterligare problematiska mönster:

  • Användarinteraktion i transaktioner. Användarinteraktion i en explicit transaktion med flera instruktioner ökar transaktionernas varaktighet avsevärt. Detta gör det mer sannolikt att dessa transaktioner överlappar varandra och att blockering och dödlägen inträffar.

    På samma sätt ökar risken för blockering och dödlägen att hålla en öppen transaktion och köra frågor mot en orelaterad databas eller system mitt i transaktionen avsevärt.

  • Transaktioner som använder objekt i olika beställningar. Dödlägen är mindre benägna att inträffa när samtidiga explicita transaktioner med flera instruktioner följer samma mönster och åtkomstobjekt i samma ordning.

Förhindra att ett dödläge upprepas

Det finns flera tillgängliga tekniker för att förhindra att dödlägen upprepas, inklusive indexjustering, tvinga planer med Query Store och ändra Transact-SQL-frågor.

  • Granska tabellens klustrade index. De flesta tabeller drar nytta av klustrade index, men ofta implementeras tabeller som heaps av misstag.

    Ett sätt att söka efter ett klustrade index är att använda den sp_helpindex system lagrade proceduren. Vi kan till exempel visa en sammanfattning av indexen i SalesLT.Product tabellen genom att köra följande instruktion:

    exec sp_helpindex 'SalesLT.Product';
    GO
    

    Granska kolumnen index_description. En tabell kan bara ha ett grupperat index. Om ett klustrat index har implementerats för tabellen innehåller index_description ordet "klustrad".

    Om det inte finns något klustrat index är tabellen en heap. I det här fallet granskar du om tabellen avsiktligt skapades som en heap för att lösa ett specifikt prestandaproblem. Överväg att implementera ett klustrade index baserat på riktlinjerna för klustrad indexdesign.

    I vissa fall kan skapande eller justering av ett grupperat index minska eller eliminera blockering i dödlägen. I andra fall kan du behöva använda ytterligare en teknik, till exempel de andra i den här listan.

  • Skapa eller ändra icke-grupperade index. Om du justerar icke-illustrerade index kan du hjälpa dina ändringsfrågor att hitta data som ska uppdateras snabbare, vilket minskar antalet uppdateringslås som krävs.

    I vårt exempel på ett dödläge innehåller frågekörningsplanen i Query Store en klustrad indexgenomsökning mot indexet PK_Product_ProductID . Dödlägesdiagrammet anger att ett delat (S) lås väntar på det här indexet är en komponent i dödläget.

    Screenshot of a query execution plan. A clustered index scan is being performed against the PK_Product_ProductID index on the Product table.

    Den här indexgenomsökningen utförs eftersom vår uppdateringsfråga måste ändra en indexerad vy med namnet vProductAndDescription. Som vi nämnde i avsnittet Sök efter mönster som ökar blockeringen i den här artikeln kan indexerade vyer som refererar till flera tabeller öka blockeringen och sannolikheten för dödlägen.

    Om vi skapar följande icke-illustrerade index i AdventureWorksLT databasen som "täcker" kolumnerna från SalesLT.Product den indexerade vyn hjälper det frågan att hitta rader mycket mer effektivt:

    CREATE INDEX ix_Product_ProductID_Name_ProductModelID on SalesLT.Product (ProductID, Name, ProductModelID);
    GO
    

    När du har skapat det här indexet upprepas inte längre dödläget.

    När dödlägen omfattar ändringar av kolumner som refereras till i begränsningar för sekundärnyckel kontrollerar du att index i referenstabellen i FOREIGN KEY effektivt kan hitta relaterade rader.

    Index kan avsevärt förbättra frågeprestanda i vissa fall, men index har även kostnader för omkostnader och hantering. Granska allmänna riktlinjer för indexdesign för att utvärdera fördelarna med index innan du skapar index, särskilt breda index och index i stora tabeller.

  • Utvärdera värdet för indexerade vyer. Ett annat alternativ för att förhindra att vårt exempel på dödläge upprepas är att släppa den SalesLT.vProductAndDescription indexerade vyn. Om den indexerade vyn inte används minskar kostnaderna för att underhålla den indexerade vyn över tid.

  • Använd ögonblicksbildisolering. I vissa fall kan inställningen av transaktionsisoleringsnivån till ögonblicksbild för en eller flera av de transaktioner som är inblandade i ett dödläge förhindra att blockering och dödlägen upprepas.

    Den här tekniken är mest sannolikt att lyckas när den används på SELECT-instruktioner när läsincheckad ögonblicksbild inaktiveras i en databas. När skrivskyddad ögonblicksbild är inaktiverad kräver SELECT-frågor som använder den lässkyddade isoleringsnivån delade (S) lås. Om du använder ögonblicksbildisolering för dessa transaktioner tar du bort behovet av delade lås, vilket kan förhindra blockering och dödlägen.

    I databaser där läsisolerad ögonblicksbildisolering har aktiverats kräver SELECT-frågor inte delade (S) lås, så dödlägen är mer benägna att inträffa mellan transaktioner som ändrar data. I fall där dödlägen uppstår mellan flera transaktioner som ändrar data kan ögonblicksbildisolering resultera i en uppdateringskonflikt i stället för ett dödläge. Detta kräver på samma sätt att en av transaktionerna försöker utföra åtgärden igen.

  • Framtvinga en plan med Query Store. Du kanske upptäcker att en av frågorna i dödläget har flera körningsplaner och att dödläget bara inträffar när en specifik plan används. Du kan förhindra att dödläget upprepas genom att tvinga fram en plan i Query Store.

  • Ändra Transact-SQL. Du kan behöva ändra Transact-SQL för att förhindra att dödläget upprepas. Ändring av Transact-SQL bör göras noggrant och ändringar bör testas noggrant för att säkerställa att data är korrekta när ändringar körs samtidigt. När du skriver om Transact-SQL bör du tänka på:

    • Orderinstruktioner i transaktioner så att de får åtkomst till objekt i samma ordning.
    • Dela upp transaktioner i mindre transaktioner när det är möjligt.
    • Om det behövs använder du frågetips för att optimera prestanda. Du kan använda tips utan att ändra programkod med hjälp av Query Store.

Hitta fler sätt att minimera dödlägen i guiden Dödlägen.

Kommentar

I vissa fall kanske du vill justera prioriteten för dödläget för en eller flera sessioner som är inblandade i ett dödläge om det är viktigt att en av sessionerna slutförs utan att försöka igen, eller när en av frågorna som ingår i dödläget inte är kritisk och alltid bör väljas som offer. Även om detta inte hindrar dödläget från att upprepas kan det minska effekten av framtida dödlägen.

Ta bort en XEvents-session

Du kanske vill lämna en XEvents-session som samlar in information om dödläge som körs på kritiska databaser under långa perioder. Tänk på att om du använder ett händelsefilmål kan det resultera i stora filer om flera dödlägen inträffar. Du kan ta bort blobfiler från Azure Storage för en aktiv spårning, förutom den fil som för närvarande skrivs till.

När du vill ta bort en XEvents-session är transact-SQL-sessionen densamma, oavsett vilken måltyp som valts.

Om du vill ta bort en XEvents-session kör du följande Transact-SQL. Innan du kör koden ersätter du namnet på sessionen med lämpligt värde.

ALTER EVENT SESSION [deadlocks] ON DATABASE
    STATE = STOP;
GO

DROP EVENT SESSION [deadlocks] ON DATABASE;
GO

Använda Azure Storage Explorer

Azure Storage Explorer är ett fristående program som förenklar arbetet med händelsefilmål som lagras i blobar i Azure Storage. Du kan använda Storage Explorer för att:

Ladda ned Azure Storage Explorer..

Nästa steg

Läs mer om prestanda i Azure SQL Database: