Тази седмица преподаваме IEPTO2 в Дъблин (и ако Ирландия не е в списъка ви с места, които трябва да видите през този живот, трябва да го добавите... тук е фантастично) и днес завърших модула за анализ на плана на заявки. Едно нещо, което обхващам, са интересни неща, които можете да намерите в плана на заявката, например:
- NoJoinPredicate (2005 и по-нови версии)
- ColumnsWithNoStatistics (2005 и по-нови)
- UnmatchedIndexes (2008 и по-нови)
- PlanAffectingConvert (2012 и по-нови версии)
Тези атрибути е добре да търсите, когато разглеждате един план или набор от планове, докато настройвате. Но ако искате да сте малко по-активни, можете да започнете да копаете кеша на плана и да ги търсите там. Разбира се, за да направите това, е необходимо да напишете малко XQuery, тъй като плановете са XML (за подробности относно схемата на showplan вижте:http://schemas.microsoft.com/sqlserver/2004/07/showplan/). Не обичам XML, макар че не поради липса на опит, и когато един от присъстващите попита дали можете да уловите заявки, които имат атрибута NoJoinPredicate чрез разширени събития, си помислих:„Каква страхотна идея, ще трябва да проверя .”
Разбира се, има събитие за това. Има събитие и за четирите, които изброих по-горе:
- missing_join_predicate
- missing_column_statistics
- unmatched_filtered_indexes
- plan_affecting_convert
Хубаво. Настройването им в сесия с разширени събития е доста лесно. В този случай бих препоръчал да използвате целта event_file, тъй като вероятно ще стартирате сесията на събитието и ще я оставите да работи за малко, преди да се върнете и да прегледате изхода. Включих няколко действия с надеждата, че тези събития не се задействат това често, така че не добавяме твърде много допълнителни разходи тук. Включих sql_text, въпреки че това не е действие, на което наистина трябва да разчитате. Джонатан е обсъждал това преди, но sql_text просто ви дава входния буфер, така че може да не получите пълната история за заявката. Поради тази причина включих и plan_handle. Предупреждението е, че в зависимост от това кога търсите плана, той може вече да не е в кеша на плана.
-- Remove event session if it exists IF EXISTS (SELECT 1 FROM [sys].[server_event_sessions] WHERE [name] = 'InterestingPlanEvents') BEGIN DROP EVENT SESSION [InterestingPlanEvents] ON SERVER END GO -- Define event session CREATE EVENT SESSION [InterestingPlanEvents] ON SERVER ADD EVENT sqlserver.missing_column_statistics ( ACTION(sqlserver.database_id,sqlserver.plan_handle,sqlserver.sql_text) WHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0)) AND [sqlserver].[database_id]>(4)) ), ADD EVENT sqlserver.missing_join_predicate ( ACTION(sqlserver.database_id,sqlserver.plan_handle,sqlserver.sql_text) WHERE ([sqlserver].[is_system]=(0) AND [sqlserver].[database_id]>(4)) ), ADD EVENT sqlserver.plan_affecting_convert ( ACTION(sqlserver.database_id,sqlserver.plan_handle,sqlserver.sql_text) WHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0)) AND [sqlserver].[database_id]>(4)) ), ADD EVENT sqlserver.unmatched_filtered_indexes ( ACTION(sqlserver.plan_handle,sqlserver.sql_text) WHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0)) AND [sqlserver].[database_id]>(4)) ) ADD TARGET package0.event_file ( SET filename=N'C:\temp\InterestingPlanEvents' /* change location if appropriate */ ) WITH (MAX_MEMORY=4096 KB,EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS, MAX_DISPATCH_LATENCY=5 SECONDS,MAX_EVENT_SIZE=0 KB,MEMORY_PARTITION_MODE=NONE, TRACK_CAUSALITY=ON,STARTUP_STATE=OFF) GO -- Start the event session ALTER EVENT SESSION [InterestingPlanEvents] ON SERVER STATE=START; GO
След като стартираме и стартираме сесията на събитието, можем да генерираме тези събития с примерния код по-долу. Имайте предвид, че този код предполага нова инсталация на AdventureWorks2014. Ако нямате такъв, може да не видите събитието missing_column_statistics да се задейства, ако сте запитали колоната [HireDate] в [HumanResources].[Employee].
-- These queries assume a FRESH restore of AdventureWorks2014 ALTER DATABASE [AdventureWorks2014] SET AUTO_CREATE_STATISTICS OFF; GO USE [AdventureWorks2014]; GO CREATE INDEX [NCI_SalesOrderHeader] ON [Sales].[SalesOrderHeader] ( [PurchaseOrderNumber], [CustomerID], [TotalDue], [DueDate] ) WHERE [SubTotal] > 10000.00; GO /* No join predicate NOTE: We clear procedure here because the event ONLY fires for the *initial* compilation */ DBCC FREEPROCCACHE; /* Not for production use */ SELECT [h].[SalesOrderID], [d].[SalesOrderDetailID], [h].[CustomerID] FROM [Sales].[SalesOrderDetail] [d], [Sales].[SalesOrderHeader] [h] WHERE [d].[ProductID] = 897; GO -- Columns with no statistics SELECT [BusinessEntityID], [NationalIDNumber], [JobTitle], [HireDate], [ModifiedDate] FROM [HumanResources].[Employee] WHERE [HireDate] >= '2013-01-01'; GO -- Unmatched Index DECLARE @Total MONEY = 10000.00; SELECT [PurchaseOrderNumber], [CustomerID], [TotalDue], [DueDate] FROM [Sales].[SalesOrderHeader] WHERE [SubTotal] > @Total; GO -- Plan Affecting Convert SELECT [BusinessEntityID], [NationalIDNumber], [JobTitle], [HireDate], [ModifiedDate] FROM [HumanResources].[Employee] WHERE [NationalIDNumber] = 345106466; GO ALTER EVENT SESSION [InterestingPlanEvents] ON SERVER STATE=STOP; GO DROP EVENT SESSION [InterestingPlanEvents] ON SERVER; GO
ЗАБЕЛЕЖКА:СЛЕД като приключите с изтеглянето на планове от кеша, можете да изпълните оператора ALTER, за да активирате опцията за автоматично създаване на статистически данни. Правейки това в този момент ще изчистите кеша на плана и ще трябва да започнете с тестването си отначало. (И също така изчакайте, докато приключите, за да пуснете индекса.)
ALTER DATABASE [AdventureWorks2014] SET AUTO_CREATE_STATISTICS ON; GO DROP INDEX [NCI_SalesOrderHeader] ON [Sales].[SalesOrderHeader]; GO
Тъй като спрях сесията на събитието, ще отворя изходния файл в SSMS, за да видя какво сме заснели:
Изход от разширени събития
За първата ни заявка с липсващ предикат за присъединяване имаме едно събитие, което се показва и мога да видя текста за заявката в полето sql_text. Но това, което наистина искам, е да погледна и плана, за да мога да взема plan_handle и да проверя sys.dm_exec_query_plan:
SELECT query_plan FROM sys.dm_exec_query_plan (0x06000700E2200333405DD12C0000000001000000000000000000000000000000000000000000000000000000);
И отваряне в SQL Sentry Plan Explorer:
Липсващ предикат за присъединяване
Планът има визуален индикатор за липсващия предикат за присъединяване във вложения цикъл (червеният X) и ако задържа курсора на мишката върху него, виждам предупреждението (и то е в XML за плана). Отлично... Вече мога да говоря с моите разработчици относно пренаписването на тази заявка.
Следващото събитие е за липсваща статистика на колона. Напълно принудих тази ситуация, като изключих AUTO_CREATE_STATISTICS за базата данни AdventureWorks2014. Не препоръчвам това по никакъв начин, форма или форма. Тази опция е активирана по подразбиране и препоръчвам винаги да я оставяте активирана. Изключването му обаче е най-лесният начин за генериране на това събитие. Отново имам заявката в полето sql_text, но отново ще използвам plan_handle, за да изтегля плана:
SELECT query_plan FROM sys.dm_exec_query_plan (0x060007004448323810921C360000000001000000000000000000000000000000000000000000000000000000);
Липсваща статистика
И отново имаме визуален знак (жълтия триъгълник с удивителния знак), за да покажем, че има проблем с плана, и отново е в XML. Оттук първо ще проверя дали AUTO_CREATE_STATISTICS е деактивиран и ако не, ще започна да изпълнявам заявката в Management Studio, за да видя дали мога да създам отново предупреждението (и да принудя статистическите данни да се създават).
Сега останалите събития са малко по-интересни.
Ще забележите, че имаме три събития unmatched_filtered_indexes. Все още трябва да определя защо, но работя върху това и ще публикувам в коментарите, ако/когато го подредя. Засега е достатъчно, че имам събитието и в рамките на събитието можем да видим и информация за обекта, така че да знам въпросния индекс:
Индекс NCI_SalesOrderHeader, посочен от липсващо събитие в индекса
И мога отново да взема plan_handle, за да намеря плана на заявката:
Несъответстващ индекс
Този път виждам предупреждението в оператора SELECT, така че знам, че има нещо, което трябва да проуча допълнително. В този случай имате опции да накарате оптимизатора да използва филтрирания индекс, когато използвате параметри, и препоръчвам да прегледате публикацията на Aaron за повече информация относно използването на филтрирани индекси.
И накрая, имаме девет събития за plan_affecting_convert. Какво по дяволите? Все още измислям това, но използвах опцията Проследяване на причинно-следствената връзка за моята сесия на събитието (при тестване), за да потвърдя, че всички събития са част от една и съща задача (те са). Ако погледнете елемента на израза в изхода, виждате, че той се променя леко (както и compile_time) и това се появява, когато погледнете подробностите на предупреждението в Plan Explorer на SQL Sentry (вижте втората екранна снимка по-долу). В изхода на събитието елементът на израза does кажете ни за каква колона става дума, което е начало, но не е почти достатъчно информация, така че отново трябва да отидем да вземем плана:
SELECT query_plan FROM sys.dm_exec_query_plan (0x0600070023747010E09E1C360000000001000000000000000000000000000000000000000000000000000000);
Планът, засягащ преобразуването
Подробности за преобразуването от плана
Отново виждаме нашия приятел, жълтия триъгълник, в оператора SELECT и в XML можем да намерим атрибута PlanAffectingConvert. Този атрибут е добавен в схемата за показване на SQL Server 2012, така че ако използвате по-ранна версия, няма да видите това в плана. Разрешаването на това предупреждение може да изисква малко повече работа – трябва да разберете къде имате несъответствие на типа данни и защо, и след това или започнете да променяте кода или схемата... и двете могат да бъдат срещнати със съпротива. Джонатан има публикация, която обсъжда по-подробно имплицитно преобразуване, което е добро място да започнете, ако не сте работили с проблеми с преобразуването преди това.
Резюме
Библиотеката от събития с разширени събития продължава да нараства и едно нещо, което трябва да имате предвид при отстраняване на неизправности в SQL Server е дали можете да получите информацията, която търсите, по друг начин. Може би защото е по-лесно (сигурно предпочитам XE пред XML!), или защото е по-ефективно, или ви дава повече подробности. Независимо дали търсите проактивно проблеми със заявките във вашата среда, или реагирате на проблем, за който някой е докладвал, но имате проблеми с намирането му, разширените събития са жизнеспособна опция, която да обмислите, особено след като към SQL Server се добавят още нови функции.