Новото свойство „Прочетени действителни редове“ в плановете за изпълнение (което в SQL Server Management Studio се показва като „Брой прочетени редове“) беше добре дошло допълнение към тунерите за производителност. Това е като да имаш нова суперсила, да можеш да кажеш значението на предиката Търсене срещу Остатъчния предикат в рамките на оператор Търсене. Обичам това, защото може да бъде наистина важно за запитването.
Нека разгледаме две заявки, които изпълнявам срещу AdventureWorks2012. Те са много прости – единият изброява хора, наречени John S, а другият изброява хора, наречени J Smith. Както всички добри телефонни указатели, ние имаме индекс за фамилно име, име.
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%';
В случай, че сте любопитни, получавам 2 реда назад от първия и 14 реда назад от втория. Всъщност не се интересувам толкова от резултатите, интересуват се от плановете за изпълнение.
Да видим какво става. Отворих по-старо копие на SQL Sentry Plan Explorer и отворих плановете си един до друг. Между другото – изпълнявах и двете заявки заедно и така и двата плана бяха в един и същ .sqlplan файл. Но мога да отворя един и същ файл два пъти в PE и с удоволствие да ги поставя един до друг в групи с раздели.
Страхотен. Те изглеждат еднакво! Виждам, че търсенето вляво произвежда два реда вместо четиринадесет – очевидно това е по-добрата заявка.
Но с по-голям прозорец щях да видя повече информация и е късмет, че изпълних двете заявки в една и съща партида.
Можете да видите, че втората заявка, която произведе 14 реда, а не 2 реда, се оценява да поеме над 80% от разходите! Ако пуснах заявките поотделно, всяка щеше да ми показва 100%.
Сега нека сравним с най-новата версия на Plan Explorer.
Нещото, което веднага ми хрумва, е предупреждението. Нека погледнем малко по-отблизо.
Предупреждението гласи „Операцията е причинила остатъчен IO. Реалният брой на прочетените редове е 2130, но броят на върнатите редове е 2." Разбира се, по-нагоре виждаме „Прочетени действителни редове“, казвайки 2130, и Действителни редове на 2.
Уау! За да намерим тези редове, трябваше да прегледаме 2130?
Виждате ли, начинът, по който върви търсенето, е да започнете с мислене за предиката за търсене. Това е този, който използва добре индекса и който всъщност кара операцията да бъде търсене. Без предикат за търсене, операцията се превръща в сканиране. Сега, ако този предикат за търсене е гарантирано, че е най-много един ред (като например когато има оператор за равенство в уникален индекс), тогава имаме еднократно търсене. В противен случай имаме сканиране на диапазон и този диапазон може да има префикс, начало и край (но не непременно и начало и край). Това дефинира редовете в таблицата, които ни интересуват за търсенето.
Но „заинтересовани“ не означава непременно „върнати“, защото може да имаме още работа. Тази работа е описана в другия предикат, който често е известен като остатъчен предикат.
Сега този остатъчен предикат може всъщност да върши по-голямата част от работата. Със сигурност е тук – филтрира нещата от 2130 реда до само 2.
Сканирането на диапазона започва в индекса на „John S“. Знаем, че ако има „John S“, това трябва да е първият ред, който може да задоволи цялото нещо. „Ian S“ не може. Така че можем да търсим в индекса в този момент, за да започнем нашето сканиране на диапазон. Ако погледнем плана XML, можем да видим това изрично.
Забележете, че нямаме префикс. Това важи, когато имате равенство в първата колона в индекса. Имаме само StartRange и EndRange. Началото на диапазона е ScanType „Greater Than or Equal“ (GE), при стойност „S, John“ (препратките към колоната извън екрана са LastName, FirstName), а Краят на диапазона е „По-малко от“ ( LT) стойността T. Когато сканирането достигне T, е готово. Няма какво повече да се направи. Търсенето вече завърши своето сканиране на обхват. И в този случай връща 2130 реда!
Освен че всъщност не връща 2130 реда, той просто чете 2130 реда. Четет се имена като Бари Сай и Кен Санчес, но се връщат само имената, които отговарят на следващата проверка – Остатъчният предикат, който гарантира, че първото име е Джон.
Записът Actual Rows Read в свойствата на оператора Index Seek ни показва тази стойност от 2130. И макар да се вижда в по-ранни версии на Plan Explorer, ние не получаваме предупреждение за това. Това е сравнително ново.
Втората ни заявка (търси J Smith) е много по-хубава и има причина, поради която се оценява, че е повече от 4 пъти по-евтина.
Тук знаем точно фамилното име (Смит), а сканирането на диапазона е на собственото име (J%).
Тук идва префиксът.
Виждаме, че нашият префикс е оператор на равенство (=, ScanType=”EQ”) и че фамилията трябва да е Смит. Все още дори не сме обмислили началото или края на диапазона, но префиксът ни казва, че диапазонът е включен в частта от индекса, където фамилното име е Смит. Сега можем да намерим редовете>=J и
Тук все още има остатъчен предикат, но това е само да се уверите, че „LIKE J%“ действително е тестван. Въпреки че ни изглежда интуитивно, че „LIKE J%“ е точно еквивалентно на „>=J и
Преди Service Pack 3 на SQL Server 2012 нямахме това свойство и за да усетим разликата между действителните прочетени и действителните редове, ще трябва да използваме флаг за проследяване 9130. Ето тези два плана с включен този TF:
Можете да видите, че този път няма предупреждение, защото операторът Seek връща всички 2130 реда. Мисля, че ако използвате версия на SQL Server, която поддържа това четене на действителни редове, трябва да спрете да използвате флага за проследяване 9130 във вашите разследвания и вместо това да започнете да разглеждате предупрежденията в Plan Explorer. Но най-вече разберете как вашите оператори вършат работата си, защото тогава ще можете да тълкувате дали сте доволни от плана или трябва да предприемете действия.
В друга публикация ще ви покажа ситуация, когато може да предпочетете да видите действителните прочетени редове да са по-високи от действителните редове.
@rob_farley