Den nya egenskapen "Actual Rows Read" i exekveringsplaner (som i SQL Server Management Studio visas som "Number of Rows Read") var ett välkommet tillskott till prestandatuners. Det är som att ha en ny superkraft, att kunna berätta betydelsen av Seek Predicate v the Residual Predicate inom en Seek-operatör. Jag älskar det här, för det kan vara väldigt viktigt för att fråga.
Låt oss titta på två frågor som jag kör mot AdventureWorks2012. De är väldigt enkla – den ena listar personer som heter John S, och den andra listar personer som heter J Smith. Som alla bra telefonböcker har vi ett index på Efternamn, Förnamn.
select FirstName, LastName from Person.Person where LastName like 'S%' and FirstName = 'John'; select FirstName, LastName from Person.Person where LastName = 'Smith' and FirstName like 'J%';
Om du är nyfiken får jag 2 rader tillbaka från den första och 14 rader tillbaka från den andra. Jag är faktiskt inte så intresserad av resultaten, jag är intresserad av genomförandeplanerna.
Låt oss se vad som händer. Jag öppnade en äldre kopia av SQL Sentry Plan Explorer och öppnade mina planer sida vid sida. För övrigt – jag hade kört båda frågorna tillsammans och så båda planerna var i samma .sqlplan-fil. Men jag kunde öppna samma fil två gånger i PE och gärna placera dem sida vid sida i flikgrupper.
Bra. De ser likadana ut! Jag kan se att sökningen till vänster producerar två rader istället för fjorton – uppenbarligen är detta den bättre frågan.
Men med ett större fönster skulle jag ha sett mer information, och det är tur att jag hade kört de två frågorna i samma parti.
Du kan se att den andra frågan, som gav 14 rader istället för 2 rader, beräknades ta över 80 % av kostnaden! Om jag skulle köra frågorna separat, skulle var och en visa mig 100 %.
Låt oss nu jämföra med den senaste versionen av Plan Explorer.
Det som hoppar upp för mig direkt är varningen. Låt oss titta lite närmare.
Varningen säger "Operationen orsakade kvarvarande IO. Det faktiska antalet lästa rader var 2 130, men antalet returnerade rader var 2.” Visst nog, längre upp ser vi "Faktiska rader lästa" som säger 2 130 och faktiska rader vid 2.
Oj! För att hitta de raderna var vi tvungna att titta igenom 2 130?
Du förstår, sättet som Seek löper på är att börja med att tänka på Seek-predikatet. Det är den som utnyttjar indexet på ett bra sätt och som faktiskt gör att operationen blir en Seek. Utan ett sökpredikat blir operationen en skanning. Om det här sökpredikatet garanterat är högst en rad (som när det har en likhetsoperator på ett unikt index), så har vi en Singleton-sökning. Annars har vi en Range Scan, och detta område kan ha ett prefix, en start och en slut (men inte nödvändigtvis både en start och en slut). Detta definierar raderna i tabellen som vi är intresserade av för sökningen.
Men "intresserad av" betyder inte nödvändigtvis "återlämnad", eftersom vi kanske har mer att göra. Det arbetet beskrivs i det andra predikatet, som ofta är känt som det återstående predikatet.
Nu när Residual Predicate faktiskt gör det mesta av jobbet. Det är verkligen här – det filtrerar ner saker från 2 130 rader till bara 2.
Range Scan startar i indexet vid "John S". Vi vet att om det finns en "John S", måste detta vara den första raden som kan tillfredsställa det hela. "Ian S" kan inte. Så vi kan söka i indexet vid den punkten för att starta vår Range Scan. Om vi tittar på Plan XML kan vi se detta explicit.
Observera att vi inte har ett prefix. Det gäller när man har en likhet i första kolumnen inom indexet. Vi har bara StartRange och EndRange. Början av intervallet är "Greater Than or Equal" (GE) ScanType, vid värdet "S, John" (kolumnreferenserna utanför skärmen är LastName, FirstName), och slutet av intervallet är "Less Than" ( LT) värdet T. När skanningen träffar T är den klar. Inget mer att göra. The Seek har nu slutfört sin Range Scan. Och i det här fallet returnerar den 2 130 rader!
Förutom att den faktiskt inte returnerar 2 130 rader, läser den bara 2 130 rader. Namn som Barry Sai och Ken Sánchez läses, men bara de namn som uppfyller nästa kontroll returneras – Residual Predicate som ser till att Förnamnet är John.
Den faktiska radläsningsposten i Index Seek-operatörens egenskaper visar oss detta värde på 2 130. Och även om det är synligt i tidigare versioner av Plan Explorer, får vi ingen varning om det. Det är relativt nytt.
Vår andra fråga (letar efter J Smith) är mycket trevligare, och det finns en anledning till att den uppskattades vara mer än fyra gånger billigare.
Här känner vi efternamnet exakt (Smith), och Range Scan är på FirstName (J%).
Det är här prefixet kommer in.
Vi ser att vårt prefix är en likhetsoperator (=, ScanType=”EQ”), och att efternamn måste vara Smith. Vi har inte ens övervägt intervallets början eller slut, men prefixet talar om för oss att intervallet ingår i den del av indexet där Efternamn är Smith. Nu kan vi hitta raderna>=J och
Det finns fortfarande ett restpredikat här, men detta är bara att se till att "LIKE J%" faktiskt testas. Även om det verkar intuitivt för oss att "LIKE J%" är exakt ekvivalent med ">=J och
Innan Service Pack 3 av SQL Server 2012 hade vi inte den här egenskapen, och för att få en känsla för skillnaden mellan faktiska rader och faktiska rader, skulle vi behöva använda spårningsflagga 9130. Här är de två planerna med den TF påslagen:
Du kan se att det inte finns någon varning den här gången, eftersom sökoperatören returnerar alla 2130 rader. Jag tror att om du använder en version av SQL Server som stöder denna faktiska radläsning, bör du sluta använda spårningsflaggan 9130 i dina undersökningar och börja titta på varningarna i Plan Explorer istället. Men framför allt, förstå hur dina operatörer gör sina saker, för då kommer du att kunna tolka om du är nöjd med planen eller om du behöver vidta åtgärder.
I ett annat inlägg kommer jag att visa dig en situation när du kanske föredrar att se faktiska rader läsa vara högre än faktiska rader.
@rob_farley