sql >> Databasteknik >  >> RDS >> Database

The Eager Index Spool och The Optimizer

Introduktion

En Eager Index Spool läser alla rader från sin underordnade operatör till en indexerad arbetstabell, innan den börjar returnera rader till sin överordnade operatör. I vissa avseenden är en ivrig indexrulle det ultimata förslaget om saknade index , men det rapporteras inte som sådant.

Kostnadsbedömning

Att infoga rader i ett indexerat arbetsbord är relativt billigt, men inte gratis. Optimeraren måste tänka på att arbetet sparar mer än det kostar. För att det ska fungera till spolens fördel måste planen beräknas förbruka rader från spolen mer än en gång. Annars kan den lika gärna hoppa över spolen och bara göra den underliggande operationen den gången.

  • För att nås mer än en gång måste spolen visas på insidan av en kapslad loops join-operator.
  • Varje iteration av slingan bör söka till ett speciellt indexspoolnyckelvärde som tillhandahålls av slingans yttre sida.

Det betyder att anslutningen måste vara en ansökan , inte en kapslad loop-anslutning . För skillnaden mellan de två, se min artikel Apply versus Nested Loops Join.

Anmärkningsvärda funktioner

Medan en ivrig indexspole bara kan visas på insidan av en kapslad slinga applicera , det är inte en "prestationsspole". En ivrig indexspole kan inte inaktiveras med spårningsflagga 8690 eller NO_PERFORMANCE_SPOOL frågetips.

Rader som infogas i indexrullen är normalt inte försorterade i indexnyckelordning, vilket kan resultera i indexsiddelning. Odokumenterad spårningsflagga 9260 kan användas för att generera en Sortering operatören före indexspolen för att undvika detta. Nackdelen är att den extra sorteringskostnaden kan avskräcka optimeraren från att över huvud taget välja spolalternativet.

SQL Server stöder inte parallella insättningar till ett b-trädindex. Det betyder att allt under en parallell ivrig indexspole löper på en enda tråd. Operatörerna under spolen är fortfarande (vilseledande) markerade med parallellitetsikonen. En tråd är vald att skriva till spolen. De andra trådarna väntar på EXECSYNC medan det är klart. När spoolen är fylld kan den läsas från med parallella trådar.

Indexspolar berättar inte för optimeraren att de stöder utdata ordnade efter spoolens indexnycklar. Om sorterad utdata från spolen krävs kan du se en onödig Sortering operatör. Ivriga indexspolar bör ofta ersättas av ett permanent index ändå, så detta är ett mindre bekymmer mycket av tiden.

Det finns fem optimeringsregler som kan generera en Eager Index Spool alternativ (känt internt som ett index on-the-fly ). Vi kommer att titta på tre av dessa i detalj för att förstå var ivriga indexspolar kommer ifrån.

SelToIndexOnTheFly

Detta är den vanligaste. Den matchar ett eller flera relationsval (a.k.a filter eller predikat) precis ovanför en dataåtkomstoperator. SelToIndexOnTheFly regeln ersätter predikaten med ett sökpredikat på en ivrig indexspole.

Demo

Ett AdventureWorks exempel på en databas visas nedan:

SELECT
    P.ProductID,
    P.[Name],
    P.SafetyStockLevel,
    TH.Quantity
FROM Production.Product AS P
CROSS APPLY
(
    SELECT MAX(TH.Quantity)
    FROM Production.TransactionHistory AS TH
    WHERE 
        TH.ProductID = P.ProductID
        AND TH.Quantity < P.SafetyStockLevel
    GROUP BY ()
) AS TH (Quantity)
WHERE
    P.[Name] LIKE N'A%';

Denna genomförandeplan har en uppskattad kostnad på 3,0881 enheter. Några intressanta platser:

  • The Nested Loops Inner Join operatör är en apply , med ProductID och SafetyStockLevel från Product tabell som yttre referenser .
  • På den första iterationen av appliceringen, Ivrig Index Spool är helt ifylld från Clustered Index Scan i TransactionHistory tabell.
  • Spolens arbetstabell har ett klustrat index inskrivet på (ProductID, Quantity) .
  • Rader som matchar predikaten TH.ProductID = P.ProductID och TH.Quantity < P.SafetyStockLevel besvaras av spolen med hjälp av dess index. Detta gäller för varje iteration av appliceringen, inklusive den första.
  • TransactionHistory tabellen skannas bara en gång.

Sorterad indata till spoolen

Det är möjligt att framtvinga sorterad input till den ivriga indexspolen, men detta påverkar den uppskattade kostnaden, som noterades i inledningen. För exemplet ovan ger aktivering av flaggan för odokumenterad spårning en plan utan en spole:

SELECT
    P.ProductID,
    P.[Name],
    P.SafetyStockLevel,
    TH.Quantity
FROM Production.Product AS P
CROSS APPLY
(
    SELECT
        MAX(TH.Quantity)
    FROM Production.TransactionHistory AS TH
    WHERE 
        TH.ProductID = P.ProductID
        AND TH.Quantity < P.SafetyStockLevel
    GROUP BY ()
) AS TH (Quantity)
WHERE
    P.[Name] LIKE N'A%'
OPTION (QUERYTRACEON 9260);

Den beräknade kostnaden för denna Indexsökning och Nyckelsökning planen är 3,11631 enheter. Detta är mer än kostnaden för planen med enbart en indexspole, men mindre än planen med en indexspole och sorterad indata.

För att se en plan med sorterad input till spolen måste vi öka det förväntade antalet loop-iterationer. Detta ger spolen en chans att betala tillbaka den extra kostnaden för Sorteringen . Ett sätt att utöka antalet förväntade rader från Product tabellen är att göra Name predikat mindre restriktivt:

SELECT
    P.ProductID,
    P.[Name],
    P.SafetyStockLevel,
    TH.Quantity
FROM Production.Product AS P
CROSS APPLY
(
    SELECT
        MAX(TH.Quantity)
    FROM Production.TransactionHistory AS TH
    WHERE 
        TH.ProductID = P.ProductID
        AND TH.Quantity < P.SafetyStockLevel
    GROUP BY ()
) AS TH (Quantity)
WHERE
    P.[Name] LIKE N'[A-P]%'
OPTION (QUERYTRACEON 9260);

Detta ger oss en exekveringsplan med sorterad input till spoolen:

JoinToIndexOnTheFly

Denna regel omvandlar en inre koppling till en ansöka , med en ivrig indexspole på insidan. Minst ett av sammanfogningspredikaten måste vara en olikhet för att denna regel ska matchas.

Detta är en mycket mer specialiserad regel än SelToIndexOnTheFly , men tanken är ungefär densamma. I det här fallet associeras urvalet (predikatet) som omvandlas till en indexspoolsökning med kopplingen. Omvandlingen från gå med till applicera gör att sammanfogningspredikatet kan flyttas från själva sammanfogningen till insidan av appliceringen.

Demo

SELECT
    P.ProductID,
    P.[Name],
    P.SafetyStockLevel,
    Quantity = MAX(TH.Quantity)
FROM Production.Product AS P
JOIN Production.TransactionHistory AS TH
    ON TH.ProductID = P.ProductID
    AND TH.Quantity < P.SafetyStockLevel
WHERE
    P.[Name] LIKE N'[A-P]%'
GROUP BY
    P.ProductID,
    P.[Name],
    P.SafetyStockLevel
OPTION (LOOP JOIN);

Som tidigare kan vi begära sorterad input till spoolen:

SELECT
    P.ProductID,
    P.[Name],
    P.SafetyStockLevel,
    Quantity = MAX(TH.Quantity)
FROM Production.Product AS P
JOIN Production.TransactionHistory AS TH
    ON TH.ProductID = P.ProductID
    AND TH.Quantity < P.SafetyStockLevel
WHERE
    P.[Name] LIKE N'[A-P]%'
GROUP BY
    P.ProductID,
    P.[Name],
    P.SafetyStockLevel
OPTION (LOOP JOIN, QUERYTRACEON 9260);

Den här gången har den extra kostnaden för sortering uppmuntrat optimeraren att välja en parallell plan.

En ovälkommen bieffekt är Sortera operatör spill till tempdb . Det totala minnesanslaget som är tillgängligt för sortering är tillräckligt, men det är jämnt fördelat mellan parallella trådar (som vanligt). Som nämnts i inledningen stöder inte SQL Server parallella infogningar till ett b-trädindex, så operatorerna under den ivriga indexspolen körs på en enda tråd. Den här enstaka tråden får bara en bråkdel av minnesanslaget, så Sortera spill till tempdb .

Denna bieffekt är kanske en anledning till att spårningsflaggan är odokumenterad och inte stöds.

SelSTVFToIdxOnFly

Den här regeln gör samma sak som SelToIndexOnTheFly , men för en strömningstabellvärderad funktion (sTVF) radkälla. Dessa sTVF:er används i stor utsträckning internt för att implementera DMV:er och DMF:er bland annat. De visas i moderna utförandeplaner som Table Valued Function operatorer (ursprungligen som fjärrtabellskanningar ).

Tidigare kunde många av dessa sTVF:er inte acceptera korrelerade parametrar från en apply. De kunde acceptera bokstaver, variabler och modulparametrar, bara inte tillämpa yttre referenser. Det finns fortfarande varningar om detta i dokumentationen, men de är något inaktuella nu.

Hur som helst, poängen är att det ibland inte är möjligt för SQL Server att klara en apply yttre referens som en parameter till en sTVF. I den situationen kan det vara vettigt att materialisera en del av sTVF-resultatet i en ivrig indexspole. Den nuvarande regeln ger den förmågan.

Demo

Nästa kodexempel visar en DMV-fråga som framgångsrikt konverterats från en koppling till en apply . Ytre referenser skickas som parametrar till den andra DMV:

-- Transformed to an apply
-- Outer reference passed as a parameter
SELECT
    DES.session_id,
    DES.login_time,
    DESWS.waiting_tasks_count
FROM sys.dm_exec_sessions AS DES
JOIN sys.dm_exec_session_wait_stats AS DESWS
    ON DESWS.session_id = DES.session_id
OPTION (FORCE ORDER);

Planegenskaperna för väntestatistiken TVF visar ingångsparametrarna. Det andra parametervärdet tillhandahålls som en yttre referens från sessionerna DMV:

Det är synd att sys.dm_exec_session_wait_stats är en vy, inte en funktion, eftersom det hindrar oss från att skriva en ansöka direkt.

Omskrivningen nedan är tillräckligt för att besegra den interna konverteringen:

-- Rewrite to avoid TVF parameter trickery
SELECT
    DES.session_id,
    DES.login_time,
    DESWS.waiting_tasks_count
FROM sys.dm_exec_sessions AS DES
JOIN sys.dm_exec_session_wait_stats AS DESWS
    ON DESWS.session_id >= DES.session_id
    AND DESWS.session_id <= DES.session_id
OPTION (FORCE ORDER);

Med session_id predikat som nu inte används som parametrar, SelSTVFToIdxOnFly regeln är gratis att konvertera dem till en ivrig indexspole:

Jag vill inte ge dig intrycket av att det behövs knepiga omskrivningar för att få en ivrig indexspole över en DMV-källa – det gör bara en enklare demo. Om du råkar stöta på en fråga med DMV-anslutningar som producerar en plan med en ivrig spole, vet du åtminstone hur den kom dit.

Du kan inte skapa index på DMV, så du kan behöva använda en hash eller merge join om exekveringsplanen inte fungerar tillräckligt bra.

Rekursiva CTE

De återstående två reglerna är SelIterToIdxOnFly och JoinIterToIdxOnFly . De är direkta motsvarigheter till SelToIndexOnTheFly och JoinToIndexOnTheFly för rekursiva CTE-datakällor. Dessa är extremt sällsynta enligt min erfarenhet, så jag kommer inte att tillhandahålla demos för dem. (Bara så att Iter en del av regelnamnet är vettigt:Det kommer från det faktum att SQL Server implementerar svansrekursion som kapslad iteration.)

När en rekursiv CTE refereras flera gånger på insidan av en tillämpas en annan regel (SpoolOnIterator ) kan cachelagra resultatet av CTE:

WITH R AS
(
    SELECT 1 AS n 
    UNION ALL
    SELECT R.n + 1 
    FROM R 
    WHERE R.n < 10
)
SELECT
    R1.n
FROM R AS R1
CROSS JOIN R AS R2;

Utförandeplanen innehåller en sällsynt Eager Row Count Spool :

Slutliga tankar

Ivriga indexspolar är ofta ett tecken på att ett användbart permanent index saknas i databasschemat. Detta är inte alltid fallet, som exemplen på funktionsvärden för streamingtabeller visar.


  1. Ansluter SAS JMP till Salesforce.com

  2. Skapa en partitionerad tabell i SQL Server (T-SQL)

  3. Hur man förvandlar en json-array till rader i postgres

  4. Det gick inte att ladda sql-moduler till databasklustret under PostgreSQL-installationen