Nie zrozum mnie źle — uwielbiam właściwość Actual Rows Read, którą widzieliśmy w planach wykonania programu SQL Server pod koniec 2015 r. Ale w dodatku SP1 dla SQL Server 2016, niecałe dwa miesiące temu (a biorąc pod uwagę, że w międzyczasie mieliśmy Święta Bożego Narodzenia, nie sądzę, by liczyła się duża część czasu od tego czasu), otrzymaliśmy kolejny ekscytujący dodatek — Szacowana liczba wierszy do odczytania (och, jest to trochę zależne od przesłanego przeze mnie elementu Connect, który pokazuje, że warto przesłać elementy Connect, i sprawia, że ten post kwalifikuje się na wtorek T-SQL w tym miesiącu, którego gospodarzem jest Brent Ozar (@brento) na temat elementów Connect ).
Podsumujmy chwilę… kiedy silnik SQL uzyskuje dostęp do danych w tabeli, używa albo operacji skanowania, albo operacji wyszukiwania. I chyba że Seek ma predykat Seek, który może uzyskać dostęp co najwyżej do jednego wiersza (ponieważ szuka dopasowania równości w zestawie kolumn – może to być tylko jedna kolumna – które są znane jako unikalne), wtedy Seek wykona RangeScan i zachowuje się jak skanowanie, tylko w podzbiorze wierszy spełnianych przez predykat Seek.
Wiersze spełnione przez Predykat Seek (w przypadku RangeScan operacji Seek) lub wszystkie wiersze w tabeli (w przypadku operacji Scan) są traktowane zasadniczo w ten sam sposób. Oba mogą zostać zakończone wcześniej, jeśli operator po jego lewej stronie nie zażąda więcej wierszy, na przykład jeśli operator Top gdzieś pobrał już wystarczającą liczbę wierszy lub jeśli operator scalający nie ma więcej wierszy do dopasowania. I oba mogą być dalej filtrowane przez predykat rezydualny (pokazany jako właściwość „Orzeczenie”), zanim wiersze zostaną nawet obsłużone przez operator Scan/Seek. Właściwości „Liczba wierszy” i „Szacowana liczba wierszy” powiedzą nam, ile wierszy ma zostać wygenerowanych przez operator, ale nie mieliśmy żadnych informacji o tym, jak wiele wierszy zostanie odfiltrowanych przez sam predykat wyszukiwania. Mogliśmy zobaczyć TableCardinality, ale było to naprawdę przydatne tylko dla operatorów skanowania, gdzie istniała szansa, że skanowanie może przeszukać całą tabelę w poszukiwaniu potrzebnych wierszy. W ogóle nie było to przydatne dla Szukających.
Zapytanie, które tutaj uruchamiam, dotyczy bazy danych WideWorldImporters i jest:
SELECT COUNT(*)FROM Sales.OrdersWHERE SalespersonPersonID =7AND YEAR(OrderDate) =2013AND MONTH(OrderDate) =4;
Ponadto mam w grze indeks:
UTWÓRZ INDEKS NIESKLASTRAROWANY rf_Orders_SalesPeople_OrderDate NA Sales.Orders (SalespersonPersonID, OrderDate);
Indeks ten obejmuje — zapytanie nie wymaga żadnych innych kolumn, aby uzyskać odpowiedź — i został zaprojektowany tak, aby można było użyć predykatu Seek na SalespersonPersonID, szybko filtrując dane do mniejszego zakresu. Funkcje na OrderDate oznaczają, że te dwa ostatnie predykaty nie mogą być używane w predykacie Seek, więc zamiast tego są przenoszone do predykatu rezydualnego. Lepsze zapytanie przefiltrowałoby te daty za pomocą OrderDate>='20130401' ORAZ OrderDate <'20130501', ale wyobrażam sobie tutaj scenariusz, który jest zbyt powszechny…
Teraz, jeśli uruchomię zapytanie, widzę wpływ predykatów rezydualnych. Plan Explorer daje nawet to przydatne ostrzeżenie, o którym pisałem wcześniej.
Widzę bardzo wyraźnie, że RangeScan ma 7276 wierszy i że predykat rezydualny filtruje to do 149. Eksplorator planów pokazuje więcej informacji na ten temat w podpowiedzi:
Ale bez uruchomienia zapytania nie widzę tych informacji. Po prostu go tam nie ma. Nieruchomości w szacunkowym planie nie mają tego:
I na pewno nie muszę przypominać – tej informacji też nie ma w pamięci podręcznej planów. Po pobraniu planu z pamięci podręcznej za pomocą:
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.preEAR%';Otworzyłem go i oczywiście nie ma śladu tej wartości 7276. Wygląda dokładnie tak samo, jak szacowany plan, który właśnie pokazałem.
Wyjmowanie planów z pamięci podręcznej to miejsce, w którym szacunkowe wartości mają swoje własne. Nie chodzi tylko o to, że wolałbym nie uruchamiać potencjalnie drogich zapytań w bazach danych klientów. Zapytanie o pamięć podręczną planu to jedno, ale uruchamianie zapytań w celu uzyskania danych rzeczywistych – to o wiele trudniejsze.
Po zainstalowaniu dodatku SQL 2016 SP1, dzięki temu elementowi Connect, mogę teraz zobaczyć właściwość Szacowana liczba wierszy do odczytu w szacowanych planach oraz w pamięci podręcznej planów. Pokazana tutaj podpowiedź operatora jest pobierana z pamięci podręcznej i łatwo widzę, że właściwość Szacowana pokazuje 7276, a także ostrzeżenie rezydualne:
To jest coś, co mógłbym zrobić na pudełku klienta, szukając w pamięci podręcznej sytuacji w problematycznych planach, w których stosunek Szacowanej liczby wierszy do odczytu i Szacowanej liczby wierszy nie jest świetny. Potencjalnie ktoś mógłby stworzyć proces sprawdzający każdy plan w pamięci podręcznej, ale to nie jest coś, co zrobiłem.
Wnikliwe czytanie pozwoli zauważyć, że Rzeczywiste wiersze, które wyszły z tego operatora, to 149, czyli znacznie mniej niż szacowane 1382,56. Ale kiedy szukam predykatów rezydualnych, które muszą sprawdzać zbyt wiele wierszy, stosunek 1,382,56 :7,276 jest nadal istotny.
Teraz, gdy stwierdziliśmy, że to zapytanie jest nieskuteczne nawet bez konieczności jego uruchamiania, sposobem na naprawienie go jest upewnienie się, że predykat rezydualny jest wystarczająco podatny na SARG. To zapytanie…
SELECT COUNT(*) FROM Sales.OrdersWHERE SalespersonPersonID =7 AND OrderDate>='20130401' AND OrderDate <'20130501';… daje takie same wyniki i nie ma predykatu rezydualnego. W tej sytuacji wartość Szacowana liczba wierszy do odczytu jest taka sama jak Szacowana liczba wierszy, a nieefektywność zniknęła:
Jak wspomniano wcześniej, ten post jest częścią wtorkowego T-SQL w tym miesiącu. Dlaczego nie udać się tam, aby zobaczyć, jakie inne prośby o funkcje zostały ostatnio przyznane?