Begrijp me niet verkeerd - ik ben dol op de eigenschap Actual Rows Read die we zagen aankomen in de uitvoeringsplannen van SQL Server eind 2015. Maar in SQL Server 2016 SP1, minder dan twee maanden geleden (en gezien het feit dat we kerst tussendoor hebben gehad, denk ik niet dat veel van de tijd sindsdien telt), kregen we nog een opwindende toevoeging - Geschat aantal te lezen rijen (oh, en dit komt enigszins door het Connect-item dat ik heb ingediend, beide demonstreren dat Connect-items de moeite waard zijn om in te dienen en dit bericht in aanmerking te laten komen voor de T-SQL-dinsdag van deze maand, gehost door Brent Ozar (@brento) over het onderwerp Connect-items ).
Laten we een moment samenvatten ... wanneer de SQL Engine toegang heeft tot gegevens in een tabel, gebruikt deze een scanbewerking of een zoekbewerking. En tenzij die Seek een Seek-predikaat heeft dat toegang heeft tot maximaal één rij (omdat het zoekt naar een gelijkheidsovereenkomst op een reeks kolommen - kan slechts een enkele kolom zijn - waarvan bekend is dat deze uniek is), dan zal de Seek een RangeScan, en gedraagt zich net als een Scan, net over de subset van rijen waaraan wordt voldaan door het Seek-predicaat.
De rijen waaraan wordt voldaan door een zoekpredicaat (in het geval van de RangeScan van een zoekbewerking) of alle rijen in de tabel (in het geval van een scanbewerking) worden in wezen op dezelfde manier behandeld. Beide kunnen vroegtijdig worden beëindigd als er geen rijen meer worden gevraagd van de operator aan de linkerkant, bijvoorbeeld als een Top-operator ergens al genoeg rijen heeft gepakt, of als een samenvoegoperator geen rijen meer heeft om tegen te matchen. En beide kunnen verder worden gefilterd door een Resterend predikaat (weergegeven als de eigenschap 'Predicaat') voordat de rijen zelfs maar worden bediend door de Scan/Seek-operator. De eigenschappen "Aantal rijen" en "Geschat aantal rijen" zouden ons vertellen hoeveel rijen naar verwachting door de operator zouden worden geproduceerd, maar we hadden geen informatie over hoe rijen zouden worden gefilterd door alleen het zoekpredikaat. We konden de TableCardinality zien, maar dit was alleen echt nuttig voor Scan-operators, waar de kans bestond dat de Scan door de hele tabel zou kijken naar de rijen die nodig waren. Het was helemaal niet nuttig voor Seeks.
De query die ik hier uitvoer is tegen de WideWorldImporters-database en is:
SELECT COUNT(*)FROM Sales.OrdersWHERE SalespersonPersonID =7AND YEAR(OrderDate) =2013AND MONTH(OrderDate) =4;
Verder heb ik een index in het spel:
MAAK NIET-GECLUSTERDE INDEX rf_Orders_SalesPeople_OrderDate OP Sales.Orders (SalespersonPersonID, OrderDate);
Deze index dekt – de zoekopdracht heeft geen andere kolommen nodig om het antwoord te krijgen – en is zo ontworpen dat een Seek-predicaat kan worden gebruikt op SalespersonPersonID, waardoor de gegevens snel worden gefilterd tot een kleiner bereik. De functies op OrderDate betekenen dat die laatste twee predikaten niet kunnen worden gebruikt binnen het Seek-predikaat, dus worden ze in plaats daarvan gedegradeerd naar het Residuele predikaat. Een betere zoekopdracht zou die datums filteren met OrderDate>='20130401' AND OrderDate <'20130501', maar ik stel me hier een scenario voor dat maar al te vaak voorkomt...
Als ik nu de query uitvoer, kan ik de impact van de resterende predikaten zien. Plan Explorer geeft zelfs die nuttige waarschuwing waar ik eerder over had geschreven.
Ik kan heel duidelijk zien dat de RangeScan 7.276 rijen is, en dat het Residual Predicate dit filtert tot 149. Plan Explorer toont meer informatie hierover in de tooltip:
Maar zonder de query uit te voeren, kan ik die informatie niet zien. Het is er gewoon niet. De eigenschappen in het geschatte plan hebben het niet:
En ik weet zeker dat ik je er niet aan hoef te herinneren - deze informatie is ook niet aanwezig in de plancache. Na het plan uit de cache te hebben gehaald met:
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) tEARIKE;%Ik opende het, en ja hoor, geen teken van die 7.276 waarde. Het ziet er precies hetzelfde uit als het geschatte plan dat ik zojuist heb laten zien.
Plannen uit de cache halen is waar de geschatte waarden tot hun recht komen. Het is niet alleen dat ik liever geen potentieel dure zoekopdrachten op klantendatabases zou uitvoeren. Het opvragen van de plancache is één ding, maar het uitvoeren van query's om de werkelijke gegevens te krijgen - dat is een stuk moeilijker.
Met SQL 2016 SP1 geïnstalleerd, kan ik dankzij dat Connect-item nu de eigenschap Geschat aantal te lezen rijen zien in geschatte plannen en in de plancache. De hier getoonde tooltip van de operator is afkomstig uit de cache en ik kan gemakkelijk zien dat de geschatte eigenschap 7.276 toont, evenals de resterende waarschuwing:
Dit is iets dat ik zou kunnen doen op een klantenbox, in de cache zoekend naar situaties in problematische plannen waar de verhouding tussen het geschatte aantal te lezen rijen en het geschatte aantal rijen niet geweldig is. Mogelijk zou iemand een proces kunnen maken dat elk plan in de cache heeft gecontroleerd, maar het is niet iets dat ik heb gedaan.
Een scherpzinnige lezing zal hebben opgemerkt dat de werkelijke rijen die uit deze operator kwamen, 149 waren, wat veel kleiner was dan de geschatte 1382.56. Maar als ik op zoek ben naar resterende predikaten die te veel rijen moeten controleren, is de verhouding van 1.382,56:7.276 nog steeds significant.
Nu we hebben ontdekt dat deze query niet effectief is zonder deze zelfs maar uit te voeren, is de manier om deze op te lossen ervoor te zorgen dat het resterende predikaat voldoende SARGable is. Deze vraag…
SELECT COUNT(*) FROM Sales.OrdersWHERE SalespersonPersonID =7 AND OrderDate>='20130401' AND OrderDate <'20130501';…geeft dezelfde resultaten en heeft geen residuaal predikaat. In deze situatie is het geschatte aantal rijen dat moet worden gelezen identiek aan het geschatte aantal rijen en is de inefficiëntie verdwenen:
Zoals eerder vermeld, maakt dit bericht deel uit van de T-SQL Tuesday van deze maand. Waarom ga je daar niet heen om te zien welke andere functieverzoeken recentelijk zijn ingewilligd?