Forstå mig ikke forkert – jeg elsker Egentlig Rows Read-egenskab, som vi så ankomme i SQL Servers eksekveringsplaner i slutningen af 2015. Men i SQL Server 2016 SP1, for mindre end to måneder siden (og i betragtning af, at vi har haft jul ind imellem, tror jeg ikke meget af tiden siden tæller), fik vi endnu en spændende tilføjelse – Anslået antal rækker, der skal læses (åh, og det er noget ned til det Connect-emne, jeg indsendte, hvilket både demonstrerer, at Connect Items er værd at indsende, og gør dette indlæg kvalificeret til denne måneds T-SQL-tirsdag, hostet af Brent Ozar (@brento) om emnet Connect-elementer ).
Lad os opsummere et øjeblik ... når SQL Engine får adgang til data i en tabel, bruger den enten en Scan-operation eller en Seek-operation. Og medmindre den Seek har et Seek-prædikat, der højst kan få adgang til én række (fordi den leder efter et lighedsmatch på et sæt kolonner – kunne kun være en enkelt kolonne – som er kendt for at være unikke), så vil Seek udføre en RangeScan, og opfører sig ligesom en scanning, lige på tværs af den delmængde af rækker, der opfyldes af søgeprædikatet.
De rækker, der opfyldes af et søgeprædikat (i tilfælde af en søgeoperations RangeScan) eller alle rækkerne i tabellen (i tilfælde af en scanningsoperation) behandles i det væsentlige på samme måde. Begge kan blive afsluttet tidligt, hvis der ikke anmodes om flere rækker fra operatøren til venstre for den, for eksempel hvis en topoperatør et eller andet sted allerede har grebet nok rækker, eller hvis en fletoperatør ikke har flere rækker at matche imod. Og begge kan filtreres yderligere af et resterende prædikat (vist som 'prædikat'-egenskaben), før rækkerne overhovedet bliver serveret af Scan/Seek-operatøren. Egenskaberne "Antal rækker" og "Estimeret antal rækker" ville fortælle os, hvor mange rækker, der forventedes at blive produceret af operatøren, men vi havde ingen information om, hvordan rækker ville blive filtreret efter kun søgeprædikatet. Vi kunne se TableCardinality, men dette var kun virkelig nyttigt for Scan-operatører, hvor der var en chance for, at Scanningen kunne se gennem hele tabellen for de rækker, den havde brug for. Det var slet ikke nyttigt for Seeks.
Forespørgslen, som jeg kører her, er mod WideWorldImporters-databasen og er:
VÆLG ANTAL(*)FRA Salg.Ordrer WHERE SalgspersonPersonID =7AND YEAR(OrderDate) =2013AND MONTH(OrderDate) =4;
Desuden har jeg et indeks i spil:
OPRET IKKE-KLUSTERET INDEX rf_Orders_SalesPeople_OrderDate ON Sales.Orders (SælgerPersonID, OrderDate);
Dette indeks dækker - forespørgslen behøver ikke andre kolonner for at få sit svar - og er designet, så et Seek-prædikat kan bruges på SalespersonPersonID, der hurtigt filtrerer dataene ned til et mindre område. Funktionerne på OrderDate betyder, at de sidste to prædikater ikke kan bruges i søgeprædikatet, så de er henvist til det resterende prædikat i stedet. En bedre forespørgsel ville filtrere disse datoer ved at bruge OrderDate>='20130401' OG OrderDate <'20130501', men jeg forestiller mig et scenario her, som er alt for almindeligt...
Nu, hvis jeg kører forespørgslen, kan jeg se virkningen af de resterende prædikater. Plan Explorer giver endda den nyttige advarsel, som jeg havde skrevet om før.
Jeg kan meget tydeligt se, at RangeScan er 7.276 rækker, og at Residual Predicate filtrerer dette ned til 149. Plan Explorer viser mere information om dette i værktøjstippet:
Men uden at køre forespørgslen kan jeg ikke se disse oplysninger. Det er der simpelthen ikke. Ejendommene i den anslåede plan har det ikke:
Og jeg er sikker på, at jeg ikke behøver at minde dig om - denne information er heller ikke til stede i planens cache. Efter at have grebet planen fra cachen ved hjælp af:
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.YEARJeg åbnede den, og ganske rigtigt, ingen tegn på den værdi på 7.276. Det ser præcis det samme ud som den estimerede plan, jeg lige har vist.
At få planer ud af cachen er, hvor de estimerede værdier kommer til deres ret. Det er ikke kun det, at jeg foretrækker faktisk ikke at køre potentielt dyre forespørgsler på kundedatabaser. At forespørge planens cache er én ting, men at køre forespørgsler for at få de faktiske oplysninger – det er meget sværere.
Med SQL 2016 SP1 installeret, takket være det Connect-element, kan jeg nu se det estimerede antal rækker, der skal læses, i estimerede planer og i plancachen. Operatørens værktøjstip, der vises her, er taget fra cachen, og jeg kan nemt se, at den estimerede egenskab viser 7.276, samt den resterende advarsel:
Dette er noget, som jeg kunne gøre på en kundeboks, hvor jeg kiggede i cachen efter situationer i problematiske planer, hvor forholdet mellem estimeret antal rækker, der skal læses, og estimeret antal rækker ikke er stort. Potentielt kunne nogen lave en proces, der tjekkede hver plan i cachen, men det er ikke noget, jeg har gjort.
Klog læsning vil have bemærket, at de faktiske rækker, der kom ud af denne operator, var 149, hvilket var meget mindre end de anslåede 1382,56. Men når jeg leder efter resterende prædikater, der skal kontrollere for mange rækker, er forholdet på 1.382,56 :7.276 stadig betydeligt.
Nu hvor vi har fundet ud af, at denne forespørgsel er ineffektiv uden overhovedet at skulle køre den, er måden at rette den på ved at sikre, at det resterende prædikat er tilstrækkeligt SARGable. Denne forespørgsel...
SELECT COUNT(*) FROM Sales.OrdersWHERE SalespersonPersonID =7 OG OrderDate>='20130401' OG OrderDate <'20130501';…giver de samme resultater og har ikke et restprædikat. I denne situation er værdien for det estimerede antal rækker, der skal læses, identisk med det estimerede antal rækker, og ineffektiviteten er væk:
Som tidligere nævnt er dette indlæg en del af denne måneds T-SQL tirsdag. Hvorfor ikke tage derover for at se, hvilke andre funktionsanmodninger der er blevet godkendt for nylig?