В предишната си публикация проучих различни методи за проследяване на автоматичните актуализации на статистиката, за да определя дали те влияят върху ефективността на заявката. Във втората половина на публикацията включих опции, една от които беше да активирам настройката на базата данни за автоматично актуализиране на статистики асинхронно. В тази публикация искам да разгледам как се променя производителността на заявката, когато автоматичната актуализация се случи преди изпълнението на заявката, и какво се случва с производителността, ако актуализацията е асинхронна.
Настройката
Започнах с копие на базата данни AdventureWorks2012 и след това създадох копие на таблицата SalesOrderHeader с над 200 милиона реда, използвайки този скрипт. Таблицата има клъстериран индекс на SalesOrderID и неклъстериран индекс на CustomerID, OrderDate, SubTotal. [Забележка:ако ще правите многократни тестове, направете резервно копие на тази база данни в този момент, за да си спестите известно време]. След като заредих данните и създадох неклъстерирания индекс, проверих броя на редовете и изчислих колко реда (приблизително) ще трябва да бъдат променени, за да се извика автоматична актуализация.
SELECTOBJECT_NAME([p].[object_id]) [TableName],[si].[name] [IndexName],[au].[type_desc] [Type],[p].[редове] [RowCount], ([p].[редове]*.20) + 500 [UpdateThreshold],[au].total_pages [PageCount],(([au].[total_pages]*8)/1024)/1024 [Общо GB]FROM [sys] ].[раздели] [p]JOIN [sys].[allocation_units] [au] ON [p].[partition_id] =[au].[container_id]JOIN [sys].[indexes] [si] на [p] .[object_id] =[si].object_id и [p].[index_id] =[si].[index_id]WHERE [p].[object_id] =OBJECT_ID(N'Sales.Big_SalesOrderHeader');
Big_SalesOrderHeader Информация за CIX и NCI
Проверих и текущата заглавка на статистиката за индекса:
DBCC SHOW_STATISTICS ('Sales.Big_SalesOrderHeader',[IX_Big_SalesOrderHeader_CustomerID_OrderDate_SubTotal]);
Статистика на NCI:в началото
След това създадох съхранената процедура, която ще използвам за тестване. Това е проста процедура, която прави заявки за Sales.Big_SalesOrderHeader и обобщава данни за продажбите по CustomerID и OrderDate за анализ:
СЪЗДАВАНЕ НА ПРОЦЕДУРА Sales.usp_GetCustomerStats@CustomerID INT,@StartDate DATETIME,@EndDate DATETIMEASBEGIN SET NOCOUNT ON; ИЗБЕРЕТЕ ИД на клиента, DATEPART(YEAR, OrderDate), DATEPART(MONTH, OrderDate), COUNT([SalesOrderID]) като изчислени ОТ [Sales].[Big_SalesOrderHeader] КЪДЕТО CustomerID =@CustomerID И OrderDate МЕЖДУ @StartDate, @EndBYDate на потребителя. DATEPART(YEAR, OrderDate), DATEPART(MONTH, OrderDate) ПОРЪЧКА ПО DATEPART(YEAR, OrderDate), DATEPART(MONTH, OrderDate);END
И накрая, преди да изпълня съхранената процедура, създадох сесия с разширени събития, за да мога да проследя продължителността на заявката с помощта на sp_statement_starting и sp_statement_completed. Добавих и събитието auto_stats, защото въпреки че не очаквах да се случи актуализация, исках да използвам същата дефиниция на сесията по-късно.
СЪЗДАВАНЕ НА СЪБИТИЯТА СЕСИЯ [StatsUpdate_QueryPerf]ВЪРХУ СЪБИТИЕТО СЪБИТИЕ НА СЪРВЪР sqlserver.auto_stats,ДОБАВЯНЕ НА СЪБИТИЕ sqlserver.sp_statement_completed(SET collect_statement=(1)),ДОБАВЯНЕ НА СЪБИТИЕ sqlserver.sp_statement'C. StatsUpdate_QueryPerf.xel')WITH (MAX_MEMORY=4096 KB,EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS,MAX_DISPATCH_LATENCY=30 СЕКУНДИ,MAX_EVENT_SIZE=0 KB,MEMORY=OFFICAL_PARTITION,MEMORY=OFF_PARTITION,ТестътЗапочнах сесията с разширени събития и след това изпълних съхранената процедура няколко пъти, използвайки различни CustomerID:
ALTER сесия на събитията [statsupdate_queryperf] на ServerState =Старт; GO EXEC SALE 11330, '2013-01-01 00:00:00.000', '2013-01-31 23:59:59.997'GOEXEC Sales.usp_GetCustomerStats 11506, '2012-10:00-10'02:01:01 -30 23:59:59.997'Goexec Sales.USP_GETCUSTOMERSTATS 17061, '2013-01-01 00:00:00.000', '2013-01-31 23:59:59.997'Goexec Sales.Usp_getCustomerStats 11711, 2013-03-03- 01 00:00:00.000', '2013-03-31 23:59:59.997'GOEXEC Sales.usp_GetCustomerStats 15131, '2013-02-01 00:00:00.0201-0201-209-79', 'GOEXEC Sales.usp_GetCustomerStats 29837, '2012-10-01 00:00:00.000', '2012-10-31 23:59:59.997'GOEXEC Sales.usp_GetCustomer 29837, '2012-10-01 00:00:00.000', '2012-10-31 23:59:59.997'GOEXEC Sales.usp_GetCustomer 15:000000000000000000000000000000000 , '2013-03-31 23:59:59,997'GOПроверих броя на изпълнението и плана, като поисках кеша на процедурите:
SELECTOBJECT_NAME([st].[objectid]),[st].[text],[qs].[execution_count],[qs].[creation_time],[qs].[last_execution_time],[qs]. [min_worker_time],[qs].[max_worker_time],[qs].[min_logical_reads],[qs].[max_logical_reads],[qs].[min_elapsed_time],[qs].[max_elapsed_time],[qp].[заявка_план] ]ОТ [sys].[dm_exec_query_stats] [qs]КРЪСНО ПРИЛАГАНЕ [sys].[dm_exec_sql_text]([qs].plan_handle) [st]КРЪСТНО ПРИЛАГАНЕ [sys].[dm_exec_query_plan]([qs].plan_handle) [qp] WHERE [st].[text] LIKE '%usp_GetCustomerStats%'И OBJECT_NAME([st].[objectid]) НЕ Е NULL;
Кеш на плана:в началото
План за заявка за съхранена процедура, използвайки SQL Sentry Plan ExplorerВиждах, че планът е създаден на 2014-04-08 18:59:39.850. С плана в кеша спрях сесията с разширени събития:
ПРОМЕНЯ СЕСИЯ НА СЪБИТИЯ [StatsUpdate_QueryPerf]В SERVERSTATE =СТОП;След това добавих около 47 милиона реда данни към таблицата, използвайки този скрипт, доста над прага, необходим за обезсилване на текущата статистика. След като добавих данните, проверих броя на редовете в таблицата:
Big_SalesOrderHeader CI:След зареждане на данниПреди да стартирам отново моята съхранена процедура, проверих кеша на плана, за да се уверя, че нищо не се е променило, и проверих, че статистиката все още не е актуализирана. Не забравяйте, че въпреки че статистическите данни са били невалидни в този момент, те няма да се актуализират, докато не се изпълни заявка, която използва статистиката (за справка:Разбиране кога статистиката ще се актуализира автоматично). За последната стъпка стартирах отново сесията с разширени събития и след това стартирах съхранената процедура няколко пъти. След тези изпълнения проверих отново кеша на плана:
Кеш на плана:След зареждане на данниexecution_count отново е 8 и ако погледнем create_time на плана, можем да видим, че е променен на 2014-04-08 19:32:52.913. Ако проверим плана, можем да видим, че той е същият, въпреки че планът е прекомпилиран:
План за заявка за съхранена процедура, използвайки SQL Sentry Plan ExplorerАнализ на резултатите от разширени събития
Взех първия файл с разширени събития – преди да бъдат заредени данните – и го отворих в SSMS, след което приложих филтър, така че да бъдат изброени само изрази от съхранената процедура:
Изход за разширени събития:След първоначално изпълнение на SPМожете да видите, че има осем (8) изпълнения на съхранената процедура, като продължителността на заявката варира леко.
Взех втория файл с разширени събития – след като данните бяха заредени – отворих го SSMS и филтрирах отново, така че да бъдат изброени само операторите от съхранената процедура, както и събитията auto_stats:
Разширени резултати от събития:изпълнение на SP след зареждане на данниРезултатът е съкратен, тъй като не е необходим всичко за показване на основния резултат. Маркираните в синьо записи представляват първото изпълнение на съхранената процедура и имайте предвид, че има няколко стъпки – актуализирането на статистиката е част от изпълнението. Инструкцията SELECT стартира (attach_activity_id.seq =3) и след това се изпълняват актуализациите на статистиката. В нашия пример всъщност имаме актуализации на три статистически данни. След като последната актуализация завърши (attach_activity_id.seq =11), тогава съхранената процедура стартира и завършва (attach_activity_id.seq =13 и attach_activity_id.seq =14). Интересното е, че има второ събитие sp_statement_starting за съхранената процедура (вероятно първото е пренебрегнато), така че общата продължителност на съхранената процедура се изчислява без актуализацията на статистиката.
В този сценарий статистическите данни се актуализират автоматично незабавно – тоест, когато се изпълни заявка, която използва невалидни статистически данни – кара заявката да се изпълнява по-дълго, въпреки че продължителността на заявката въз основа на събитието sp_statement_completed все още е по-малка от 14 000. Крайният резултат е, че има няма полза за ефективността на заявките, тъй като планът е абсолютно същият преди и след актуализирането на статистиката. В този сценарий планът на заявката и продължителността на изпълнение не се променят след добавяне на повече данни към таблицата, така че актуализацията на статистиката само възпрепятства нейната производителност. Сега нека видим какво се случва, когато активираме опцията за автоматично актуализиране на статистиката асинхронно.
Тестът, версия 2
Започваме с възстановяване на архива, който взех преди да започнем първия тест. Пресъздадох съхранената процедура и след това промених опцията за база данни, за да актуализирам статистиката асинхронно:
ИЗПОЛЗВАЙТЕ [master];GOALTER DATABASE [AdventureWorks2012_Big] НАСТРОЙТЕ AUTO_UPDATE_STATISTICS_ASYNC ВКЛЮЧЕНО С NO_WAITGOЗапочнах сесията с разширени събития и отново изпълних съхранената процедура няколко пъти, използвайки различни CustomerID:
ALTER сесия на събитията [statsupdate_queryperf] на ServerState =Старт; GO EXEC SALES.USP_GETCUSTOMERSTATS11331, '2012-08-01 00:00:00.000', '2012-08-31 23:59:59.997'Goexec Sales.USP_GETCUSTOMERSTATS11330, '2013-01-01 00:00:00.000', '2013-01-31 23:59:59.997'GOEXEC Sales.usp_GetCustomerStats11506, '2012-11-01 00:00:01-02:01-20:01-02 :59:59.997'GOEXEC Sales.usp_GetCustomerStats17061, '2013-01-01 00:00:00.000', '2013-01-31 23:59:59.997'GOEXEC Sales:012001:0100010100001000000000000000 00.000 ',' 2013-03-31 23:59:59.997'Goexec Sales.USP_GETCUSTOMERSTATS15131, '2013-02-01 00:00:00.000', '2013-02-28 23:59:59.997'Goexec Sales.USP_GETCUSTOMERSTATS29837, GOEXEC SALE '2012-10-01 00:00:00.000', '2012-10-31 23:59:59.997'GOEXEC Sales.usp_GetCustomerStats15750, '2013-03-01 00:00:00-02:00-30'13:00 :59:59.997'GOПроверих броя на изпълнението и плана, като поисках кеша на процедурите:
Кеш на плана:в началото, тест 2
План за заявка за съхранена процедура, използвайки SQL Sentry Plan ExplorerЗа този тест планът е създаден на 2014-04-08 21:15:55.490. Спрях сесията с разширени събития и отново добавих около 47 милиона реда данни към таблицата, като използвах същата заявка както преди.
След като данните бяха добавени, проверих кеша на плана, за да се уверя, че нищо не се е променило, и проверих, че статистиката все още не е актуализирана. Накрая стартирах сесията на разширени събития отново и след това стартирах съхранената процедура още осем пъти. Последно надникване в кеша на плана показа execution_count на 16 и create_time от 2014-04-08 21:15:55.490. execution_count и create_time демонстрират, че статистическите данни не са актуализирани, тъй като планът все още не е бил изтрит от кеша (ако беше, щяхме да имаме по-късно create_time и execution_count от 8).
Кеш на плана:след зареждане на данни, тест 2Ако отворим изхода за разширени събития от след зареждането на данните в SSMS и отново филтрираме, така че да виждаме само изрази от съхранената процедура, както и събития auto_stats, ще открием това (обърнете внимание, че изходът е разделен на две снимки на екрана):
Разширен изход за събития:тест 2, изпълнение на SP след зареждане на данни, част I
Разширен изход за събития:тест 2, изпълнение на SP след зареждане на данни, част IIСъбитията за изпълнение на първото извикване на съхранената процедура са маркирани в синьо – започват от 2014-04-08 21:54:14.9480607 и има седем (7) събития. Имайте предвид, че има три (3) събития auto_stats, но нито едно от тях всъщност не е завършено, както видяхме, когато опцията за автоматично актуализиране на статистиката асинхронно беше деактивирана. Ще забележите, че автоматичната актуализация започва за една от статистическите данни почти веднага (2014-04-08 21:54:14.9481288) и трите събития имат червения текст „Актуализация на статистиката №1“ до тях. Тази актуализация на статистическите данни приключва в 2014-04-08 21:54:16.5392219, малко по-малко от две секунди след началото, но след като всички други изпълнения на процедурата са завършени. Ето защо execution_count от sys.dm_exec_query_stats показва 16. От изхода на XE можем да видим, че другите актуализации на статистиката след това завършват (актуализация на статистиката #2 и актуализация на статистиката #3). Всички актуализации са асинхронни с изпълнението на първоначалната съхранена процедура.
Резюме
Както можете да видите, автоматичните актуализации на статистическите данни имат потенциал да повлияят негативно на ефективността на заявката. Степента на въздействие ще зависи от количеството данни, които трябва да бъдат прочетени за актуализиране на статистиката, и от системните ресурси. В някои случаи производителността на заявката се увеличава само с милисекунди и най-вероятно е незабележима за потребителите. Друг път продължителността може да се увеличи драстично, което след това се отразява на изживяването на крайния потребител. В случай, че планът на заявката не се промени след актуализация на статистическите данни, си струва да помислите за активиране на опцията за автоматично актуализиране на статистиката асинхронно, за да смекчите въздействието върху производителността на заявката.