Förstå mig inte fel – jag älskar egenskapen Actual Rows Read som vi såg anlända i SQL Servers exekveringsplaner i slutet av 2015. Men i SQL Server 2016 SP1, för mindre än två månader sedan (och med tanke på att vi har haft jul emellan, jag tror inte att mycket av tiden sedan dess räknas), fick vi ytterligare ett spännande tillskott – Uppskattat antal rader som ska läsas (Åh, och det här beror lite på Connect-objektet jag skickade in, vilket både visar att Connect Items är värt att skicka in och gör det här inlägget kvalificerat för denna månads T-SQL-tisdag, värd av Brent Ozar (@brento) om ämnet Connect-objekt ).
Låt oss sammanfatta ett ögonblick ... när SQL Engine kommer åt data i en tabell använder den antingen en skanningsoperation eller en sökoperation. Och om inte den Seek har ett Seek-predikat som kan komma åt högst en rad (eftersom den letar efter en jämställdhetsmatchning på en uppsättning kolumner – kan bara vara en enda kolumn – som är kända för att vara unika), så kommer Seek att utföra en RangeScan, och beter sig precis som en skanning, tvärs över delmängden av rader som är uppfyllda av sökpredikatet.
Raderna som tillfredsställs av ett sökpredikat (i fallet med en sökoperations RangeScan) eller alla rader i tabellen (i fallet med en skanningsoperation) behandlas i huvudsak på samma sätt. Båda kan avslutas i förtid om inga fler rader begärs från operatören till vänster, till exempel om en toppoperatör någonstans redan har tagit tillräckligt med rader, eller om en sammanslagningsoperatör inte har fler rader att matcha mot. Och båda kan filtreras ytterligare av ett återstående predikat (visas som egenskapen "Predikat") innan raderna ens serveras av Scan/Seek-operatören. Egenskaperna "Antal rader" och "Uppskattat antal rader" skulle berätta för oss hur många rader som förväntades produceras av operatören, men vi hade ingen information om hur rader skulle filtreras med bara sökpredikatet. Vi kunde se TableCardinality, men detta var bara riktigt användbart för Scan-operatörer, där det fanns en chans att Scan kan titta igenom hela tabellen efter de rader den behövde. Det var inte alls användbart för Seeks.
Frågan som jag kör här är mot WideWorldImporters-databasen och är:
VÄLJ ANTAL(*)FRÅN Sales.OrdersWHERE SalespersonPersonID =7AND YEAR(OrderDate) =2013AND MONTH(OrderDate) =4;
Dessutom har jag ett index på gång:
SKAPA INKLUSTERAT INDEX rf_Orders_SalesPeople_OrderDate ON Sales.Orders (SäljarePersonID, OrderDate);
Det här indexet täcker – frågan behöver inga andra kolumner för att få sitt svar – och har utformats så att ett Seek Predicate kan användas på SalespersonPersonID, vilket snabbt filtrerar ner data till ett mindre intervall. Funktionerna på OrderDate innebär att de två sista predikaten inte kan användas inom sökpredikatet, så de hänvisas till det återstående predikatet istället. En bättre fråga skulle filtrera dessa datum med OrderDate>='20130401' OCH OrderDate <'20130501', men jag föreställer mig ett scenario här som är alltför vanligt...
Nu, om jag kör frågan, kan jag se effekten av de återstående predikaten. Plan Explorer ger till och med den användbara varningen som jag skrivit om tidigare.
Jag kan se mycket tydligt att RangeScan är 7 276 rader, och att Residual Predicate filtrerar ner detta till 149. Plan Explorer visar mer information om detta i verktygstipset:
Men utan att köra frågan kan jag inte se den informationen. Det finns helt enkelt inte där. Fastigheterna i den beräknade planen har inte det:
Och jag är säker på att jag inte behöver påminna dig – den här informationen finns inte heller i plancachen. Efter att ha hämtat planen från cachen med:
SELECT p.query_plan, t.textFROM sys.dm_exec_cached_plans cCROSS APPLY sys.dm_exec_query_plan(c.plan_handle) pCROSS APPLY sys.dm_exec_sql_text(c.plan_handle) tWHERE 't.YEAR t.YEAR t.Jag öppnade den, och visst, inga tecken på det värdet på 7 276. Den ser precis ut som den beräknade planen jag just visade.
Att få ut planer ur cachen är där de uppskattade värdena kommer till sin rätt. Det är inte bara det att jag föredrar att faktiskt inte köra potentiellt dyra frågor på kunddatabaser. Att fråga efter planens cache är en sak, men att köra frågor för att få fakta – det är mycket svårare.
Med SQL 2016 SP1 installerat, tack vare det Connect-objektet, kan jag nu se det uppskattade antalet rader som ska läsas i uppskattade planer och i plancachen. Operatörens verktygstips som visas här är hämtat från cachen, och jag kan enkelt se den uppskattade egenskapen som visar 7 276, samt den kvarvarande varningen:
Detta är något som jag skulle kunna göra på en kundbox, leta i cachen efter situationer i problematiska planer där förhållandet mellan uppskattat antal rader som ska läsas och uppskattat antal rader inte är bra. Eventuellt skulle någon kunna göra en process som kontrollerade varje plan i cachen, men det är inget jag har gjort.
Snygg läsning kommer att ha märkt att de faktiska raderna som kom ut från denna operatör var 149, vilket var mycket mindre än de uppskattade 1382,56. Men när jag letar efter restpredikat som måste kontrollera för många rader, är förhållandet 1 382,56 :7 276 fortfarande betydande.
Nu när vi har funnit att den här frågan är ineffektiv utan att ens behöva köra den, är sättet att fixa det att se till att det återstående predikatet är tillräckligt SARG-bart. Den här frågan...
VÄLJ ANTAL(*) FRÅN Sales.OrdersWHERE SalespersonPersonID =7 OCH OrderDate>='20130401' OCH OrderDate <'20130501';…ger samma resultat och har inget restpredikat. I den här situationen är det uppskattade antalet rader som ska läsas identiskt med det uppskattade antalet rader, och ineffektiviteten är borta:
Som nämnts tidigare är det här inlägget en del av denna månads T-SQL-tisdag. Varför inte gå dit för att se vilka andra funktionsförfrågningar som har beviljats nyligen?