Dela via


Felsöka frågor som verkar köras oändligt i SQL Server

Den här artikeln innehåller felsökningsvägledning för problem där en Microsoft SQL Server-fråga tar för lång tid att avsluta (timmar eller dagar).

Symptoms

Den här artikeln fokuserar på frågor som verkar köras eller kompileras utan slut. Processoranvändningen fortsätter alltså att öka. Den här artikeln gäller inte för frågor som blockeras eller väntar på en resurs som aldrig släpps. I dessa fall förblir CPU-användningen konstant eller ändras bara något.

Viktigt!

Om en fråga är kvar för att fortsätta köras kan den så småningom slutföras. Den här processen kan ta bara några sekunder eller flera dagar. I vissa situationer kan frågan vara oändlig, till exempel när en WHILE-loop inte avslutas. Termen "never-ending" används här för att beskriva uppfattningen av en fråga som inte slutförs.

Orsak

Vanliga orsaker till långvariga (oändliga) frågor är:

  • Kapslade loopkopplingar (NL) i mycket stora tabeller: På grund av NL-kopplingar kan en fråga som ansluter till tabeller som har många rader köras under lång tid. Mer information finns i Kopplingar.
    • Ett exempel på en NL-koppling är användningen av TOP, FASTeller EXISTS. Även om en hash- eller sammanslagningskoppling kan vara snabbare kan optimeraren inte använda någon av operatorerna på grund av radmålet.
    • Ett annat exempel på en NL-koppling är användningen av en ojämlikhetskopplingspredikat i en fråga. Till exempel SELECT .. FROM tab1 AS a JOIN tab 2 AS b ON a.id > b.id. Optimeraren kan inte heller använda sammanfognings- eller hashkopplingar här.
  • Inaktuell statistik: Frågor som väljer en plan baserat på inaktuell statistik kan vara suboptimala och ta lång tid att köra.
  • Ändlösa loopar: T-SQL-frågor som använder WHILE-loopar kan vara felaktigt skrivna. Den resulterande koden lämnar aldrig loopen och körs oändligt. De här frågorna slutar verkligen aldrig. De springer tills de dödas manuellt.
  • Komplexa frågor som har många kopplingar och stora tabeller: Frågor som omfattar många anslutna tabeller skulle vanligtvis ha komplexa frågeplaner som kan ta lång tid att köra. Det här scenariot är vanligt i analysfrågor som inte filtrerar bort rader och som omfattar ett stort antal tabeller.
  • Index saknas: Frågor kan köras betydligt snabbare om lämpliga index används i tabeller. Med index kan du välja en delmängd av data för att ge snabbare åtkomst.

Lösning

Steg 1: Identifiera oändliga frågor

Leta efter en oändlig fråga som körs i systemet. Du måste avgöra om en fråga har lång körningstid, lång väntetid (fastnat på en flaskhals) eller en lång kompileringstid.

1.1 Kör en diagnostik

Kör följande diagnostikfråga på din SQL Server-instans där den oändliga frågan är aktiv:

DECLARE @cntr INT = 0

WHILE (@cntr < 3)
BEGIN
    SELECT TOP 10 s.session_id,
                    r.status,
                    CAST(r.cpu_time / (1000 * 60.0) AS DECIMAL(10,2)) AS cpu_time_minutes,
                    CAST(r.total_elapsed_time / (1000 * 60.0) AS DECIMAL(10,2)) AS elapsed_minutes,
                    r.logical_reads,
                    r.wait_time,
                    r.wait_type,
                    r.wait_resource,
                    r.reads,
                    r.writes,
                    SUBSTRING(st.TEXT, (r.statement_start_offset / 2) + 1,
                    ((CASE r.statement_end_offset
                        WHEN -1 THEN DATALENGTH(st.TEXT)
                        ELSE r.statement_end_offset
                    END - r.statement_start_offset) / 2) + 1) AS statement_text,
                    COALESCE(QUOTENAME(DB_NAME(st.dbid)) + N'.' + QUOTENAME(OBJECT_SCHEMA_NAME(st.objectid, st.dbid)) 
                    + N'.' + QUOTENAME(OBJECT_NAME(st.objectid, st.dbid)), '') AS command_text,
                    r.command,
                    s.login_name,
                    s.host_name,
                    s.program_name,
                    s.last_request_end_time,
                    s.login_time,
                    r.open_transaction_count,
                    atrn.name as transaction_name,
                    atrn.transaction_id,
                    atrn.transaction_state
        FROM sys.dm_exec_sessions AS s
        JOIN sys.dm_exec_requests AS r ON r.session_id = s.session_id 
                CROSS APPLY sys.Dm_exec_sql_text(r.sql_handle) AS st
        LEFT JOIN (sys.dm_tran_session_transactions AS stran 
                JOIN sys.dm_tran_active_transactions AS atrn
                ON stran.transaction_id = atrn.transaction_id)
        ON stran.session_id =s.session_id
        WHERE r.session_id != @@SPID
        ORDER BY r.cpu_time DESC
    
    SET @cntr = @cntr + 1
WAITFOR DELAY '00:00:05'
END

1.2 Granska utdata

Det finns flera scenarier som kan orsaka att en fråga körs under lång tid: lång körning, lång väntan och lång kompilering. Mer information om varför en fråga kan köras långsamt finns i Köra jämfört med väntar: varför är frågorna långsamma?

Lång körningstid

Felsökningsstegen i den här artikeln gäller när du får utdata som liknar följande, där CPU-tiden ökar proportionellt till den förflutna tiden utan betydande väntetider.

session_id status cpu_time_minutes elapsed_time_minutes logical_reads wait_time_minutes wait_type
56 körs 64.40 23.50 0 0.00 NULL

Frågan körs kontinuerligt om den har:

  • En ökande CPU-tid
  • Status för running eller runnable
  • Minimal eller noll väntetid
  • Ingen wait_type

I den här situationen läser frågan rader, sammanfogar, bearbetar resultat, beräknar eller formaterar. Dessa aktiviteter är alla CPU-bundna åtgärder.

Kommentar

Ändringar i logical_reads är inte relevanta i det här fallet eftersom vissa CPU-bundna T-SQL-begäranden, till exempel att utföra beräkningar eller en WHILE loop, kanske inte gör några logiska läsningar alls.

Om den långsamma frågan uppfyller dessa kriterier fokuserar du på att minska körningen. Att minska körningen innebär vanligtvis att minska antalet rader som frågan måste bearbeta under hela sin livslängd genom att tillämpa index, skriva om frågan eller uppdatera statistik. Mer information finns i avsnittet Lösning .

Lång väntetid

Den här artikeln gäller inte för scenarier med långa väntetider. I ett väntescenario kan du få utdata som liknar följande exempel där processoranvändningen inte ändras eller ändras något eftersom sessionen väntar på en resurs:

session_id status cpu_time_minutes elapsed_time_minutes logical_reads wait_time_minutes wait_type
56 upphängd 0.03 4.20 50 4.10 LCK_M_U

Väntetypen anger att sessionen väntar på en resurs. En lång tid och lång väntetid indikerar att sessionen väntar på det mesta av sin livslängd för den här resursen. Kort CPU-tid indikerar att lite tid faktiskt användes för att bearbeta frågan.

Information om hur du felsöker frågor som är långa på grund av väntetider finns i Felsöka långsamma frågor i SQL Server.

Lång kompileringstid

I sällsynta fall kan du observera att CPU-användningen ökar kontinuerligt över tid men inte drivs av frågekörningen. I stället kan en alltför lång kompilering (parsning och kompilering av en fråga) vara orsaken. I dessa fall kontrollerar du utdatakolumnen transaction_name efter värdet sqlsource_transform. Det här transaktionsnamnet anger en kompilering.

Steg 2: Samla in diagnostikloggar manuellt

När du har fastställt att det finns en oändlig fråga i systemet kan du samla in frågans plandata för att felsöka ytterligare. Om du vill samla in data använder du någon av följande metoder, beroende på din version av SQL Server.

Följ dessa steg för att samla in diagnostikdata med hjälp av SQL Server Management Studio (SSMS):

  1. Samla in xml-koden för den uppskattade frågekörningsplanen .

  2. Granska frågeplanen för att ta reda på om data visar tydliga indikationer på vad som orsakar långsamheten. Exempel på typiska indikationer är:

    • Tabell- eller indexgenomsökningar (titta på uppskattade rader)
    • Kapslade loopar som drivs av en enorm datauppsättning för yttre tabeller
    • Kapslade loopar som har en stor gren på den inre sidan av slingan
    • Tabellpooler
    • Funktioner i SELECT listan som tar lång tid att bearbeta varje rad
  3. Om frågan körs snabbare när som helst kan du samla in "snabba" körningar (faktisk XML-körningsplan) för att jämföra resultat.

Använda SQL LogScout för att samla in oändliga frågor

Du kan använda SQL LogScout för att samla in loggar medan en oändlig fråga körs. Använd det oändliga frågescenariot med följande kommando:

.\SQL_LogScout.ps1 -Scenario "NeverEndingQuery" -ServerName "SQLInstance"

Kommentar

Den här logginsamlingsprocessen kräver att den långa frågan förbrukar minst 60 sekunders CPU-tid.

SQL LogScout samlar in minst tre frågeplaner för varje fråga med hög CPU-användning. Du hittar filnamn som liknar servername_datetime_NeverEnding_statistics_QueryPlansXml_Startup_sessionId_#.sqlplan. Du kan använda dessa filer i nästa steg när du granskar planer för att identifiera orsaken till lång frågekörning.

Steg 3: Granska de insamlade planerna

I det här avsnittet beskrivs hur du granskar insamlade data. Den använder flera XML-frågeplaner (med tillägget .sqlplan) som samlas in i Microsoft SQL Server 2016 SP1 och senare versioner.

Jämför körningsplaner genom att följa dessa steg:

  1. Öppna en tidigare sparad frågekörningsplanfil (.sqlplan).

  2. Högerklicka i ett tomt område i körningsplanen och välj Jämför Showplan.

  3. Välj den andra frågeplansfilen som du vill jämföra.

  4. Leta efter tjocka pilar som anger ett stort antal rader som flödar mellan operatorer. Välj sedan operatorn före eller efter pilen och jämför antalet faktiska rader mellan de två planerna.

  5. Jämför den andra och tredje planen för att lära dig om det största flödet av rader inträffar i samma operatorer.

    Till exempel:

    Skärmbild som visar jämförelse av frågeplaner i SSMS.

Steg 4: Lösning

  1. Kontrollera att statistiken uppdateras för de tabeller som används i frågan.

  2. Leta efter saknade indexrekommendationer i frågeplanen och tillämpa alla som du hittar.

  3. Förenkla frågan:

    • Använd mer selektiva WHERE predikat för att minska de data som bearbetas i förväg.
    • Bryt isär den.
    • Välj vissa delar i temporära tabeller och anslut dem senare.
    • Ta bort TOP, EXISTSoch FAST (T-SQL) i de frågor som körs under lång tid på grund av ett radmål för optimeraren.
    • Undvik att använda Vanliga tabelluttryck (CTE) i sådana fall eftersom de kombinerar instruktioner till en enda stor fråga.
  4. Prova att använda frågetips för att skapa en bättre plan:

    • HASH JOIN eller MERGE JOIN tips
    • FORCE ORDER antydan
    • FORCESEEK antydan
    • RECOMPILE
    • USE PLAN N'<xml_plan>' (om du har en snabb frågeplan som du kan framtvinga)
  5. Använd Query Store (QDS) för att framtvinga en bra känd plan om en sådan plan finns och om din SQL Server-version stöder Query Store.