Не ме разбирайте погрешно – обичам свойството Actual Rows Read, което видяхме да пристигне в плановете за изпълнение на SQL Server в края на 2015 г. Но в SQL Server 2016 SP1, преди по-малко от два месеца (и като се има предвид, че имахме Коледа между тях, не мисля, че голяма част от времето оттогава се зачита), получихме още едно вълнуващо допълнение – Приблизителен брой редове за четене (о, и това донякъде се свежда до елемента Connect, който изпратих, като едновременно демонстрира, че Connect Items си струва да бъдат изпратени, и прави тази публикация подходяща за този месец T-SQL вторник, хостван от Brent Ozar (@brento) по темата Connect елементи ).
Нека обобщим момент... когато SQL Engine има достъп до данни в таблица, той използва или операция Scan, или операция Seek. И освен ако това търсене няма предикат за търсене, който може да има достъп до най-много един ред (защото търси съвпадение на равенство в набор от колони – може да бъде само една колона – за които се знае, че са уникални), тогава търсенето ще извърши RangeScan и се държи точно като сканиране, точно в подмножеството от редове, които са удовлетворени от предиката за търсене.
Редовете, удовлетворени от предикат за търсене (в случай на RangeScan на операцията за търсене) или всички редове в таблицата (в случай на операция за сканиране), се третират по същество по същия начин. И двете могат да бъдат прекратени по-рано, ако не са поискани повече редове от оператора отляво, например ако Топ оператор някъде вече е грабнал достатъчно редове, или ако операторът за сливане няма повече редове, с които да съвпада. И двете могат да бъдат филтрирани допълнително чрез Остатъчен предикат (показан като свойството „Предикат“), преди редовете дори да бъдат обслужени от оператора Scan/Seek. Свойствата „Брой редове“ и „Прогнозен брой редове“ ще ни кажат колко реда се очаква да бъдат произведени от оператора, но нямахме никаква информация за това как може редовете да бъдат филтрирани само от предиката за търсене. Можехме да видим TableCardinality, но това беше наистина полезно само за оператори Scan, където имаше шанс Scan да прегледа цялата таблица за редовете, от които се нуждае. Изобщо не беше полезно за Seeks.
Заявката, която изпълнявам тук, е към базата данни WideWorldImporters и е:
ИЗБЕРЕТЕ БРОЙ(*)ОТ Sales.OrdersWHERE SalespersonPersonID =7AND YEAR(OrderDate) =2013AND MONTH(OrderDate) =4;
Освен това имам в игра индекс:
СЪЗДАВАНЕ НА НЕКЛУСТРИРАН ИНДЕКС rf_Orders_SalesPeople_OrderDate ON Sales.Orders (SalespersonPersonID, OrderDate);
Този индекс обхваща – заявката не се нуждае от други колони, за да получи отговора си – и е проектиран така, че предикатът за търсене да може да се използва на SalespersonPersonID, бързо филтрирайки данните до по-малък диапазон. Функциите на OrderDate означават, че последните два предиката не могат да се използват в предиката за търсене, така че вместо това се пренасочват към остатъчния предикат. По-добра заявка би филтрирала тези дати с помощта на OrderDate>='20130401' И OrderDate <'20130501', но аз си представям сценарий тук, който е твърде често срещан...
Сега, ако стартирам заявката, мога да видя въздействието на Остатъчните предикати. Plan Explorer дори дава това полезно предупреждение, за което писах преди.
Много ясно виждам, че RangeScan е 7276 реда и че Остатъчният предикат филтрира това до 149. Plan Explorer показва повече информация за това в подсказката:
Но без да стартирам заявката, не мога да видя тази информация. Просто го няма. Имотите в прогнозния план го нямат:
И съм сигурен, че няма нужда да ви напомням – тази информация също не присъства в кеша на плана. След като вземете плана от кеша с помощта на:
ИЗБЕРЕТЕ 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) tWHERLIKE';'>Отворих го и със сигурност нямаше следа от тази стойност от 7276. Изглежда точно същото като прогнозния план, който току-що показах.
Изваждането на планове от кеша е мястото, където приблизителните стойности идват сами по себе си. Не само, че бих предпочел всъщност да не изпълнявам потенциално скъпи заявки в клиентски бази данни. Запитването на кеша на плана е едно нещо, но изпълняването на заявки за получаване на действителните данни – това е много по-трудно.
С инсталиран SQL 2016 SP1, благодарение на този елемент Connect, вече мога да видя свойството Estimated Number of Rows to be Read в прогнозните планове и в кеша на плана. Подсказката на оператора, показана тук, е взета от кеша и лесно мога да видя, че Estimated property показва 7,276, както и остатъчното предупреждение:
Това е нещо, което бих могъл да направя на клиентска кутия, търсейки в кеша за ситуации в проблемни планове, при които съотношението на прогнозния брой редове за четене и прогнозния брой редове не е голямо. Потенциално някой би могъл да направи процес, който да проверява всеки план в кеша, но това не е нещо, което съм правил.
Проницателното четене ще забележи, че действителните редове, излезли от този оператор, са 149, което е много по-малко от изчислените 1382,56. Но когато търся остатъчни предикати, които трябва да проверяват твърде много редове, съотношението от 1382,56 :7276 все още е значително.
Сега, когато открихме, че тази заявка е неефективна, без дори да е необходимо да я стартираме, начинът да я поправим е да се уверим, че остатъчният предикат е достатъчно SARGable. Тази заявка...
ИЗБЕРЕТЕ БРОЙ(*) ОТ Sales.OrdersWHERE SalespersonPersonID =7 И Дата на поръчка>='20130401' И Дата на поръчка <'20130501';... дава същите резултати и няма остатъчен предикат. В тази ситуация стойността на прогнозния брой редове за четене е идентична с прогнозния брой редове и неефективността е изчезнала:
Както споменахме по-рано, тази публикация е част от T-SQL вторник този месец. Защо не отидете там, за да видите какви други заявки за функции са били предоставени наскоро?