Database
 sql >> база данни >  >> RDS >> Database

Друга причина да избягвате sp_updatestats

Преди това писах в блог защо не обичам sp_updatestats. Наскоро открих друга причина, че не ми е приятел. TL;DR:Не актуализира статистически данни за индексираните изгледи. Сега документацията не твърди, че го прави, така че тук няма грешка. Документацията на MSDN ясно гласи:

Изпълнява UPDATE STATISTICS срещу всички потребителски дефинирани и вътрешни таблици в текущата база данни.

Но... колко от вас помислиха за вашите индексирани изгледи и се чудеха дали те са актуализирани? Признавам, че не го направих. Забравям за индексираните изгледи, което е жалко, защото те могат да бъдат наистина мощни, когато се използват по подходящ начин. Те също могат да бъдат кошмар за разгадаване, когато отстранявате проблеми, но няма да споря за използването им днес. Искам само да сте наясно, че те не се актуализират от sp_updatestats и да видите какви опции имате.

Настройка

Тъй като Световните серии току-що приключиха, ще използваме базата данни за бейзбол за нашето тестване. Можете да го изтеглите от страницата SQLskills ресурси. След като бъде възстановено, ще създадем копие на таблицата dbo.Players, наречена dbo.PlayerInfo, ще заредим няколко хиляди реда в нея и след това ще създадем индексиран изглед, който присъединява новата ни таблица към таблицата PitchingPost:

ИЗПОЛЗВАЙТЕ [BaseballData]; ИЗПОЛЗВАЙТЕ СЪЗДАЙТЕ ТАБЛИЦА [dbo].[PlayerInfo]( [lahmanID] [int] НЕ NULL, [playerID] [varchar](10) NULL DEFAULT (NULL), [managerID] [varchar]( 10) NULL DEFAULT (NULL), [hofID] [varchar](10) NULL DEFAULT (NULL), [birthYear] [int] NULL DEFAULT (NULL), [birthMonth] [int] NULL DEFAULT (NULL), [birthDay] [int] NULL DEFAULT (NULL), [birthCountry] [varchar](50) NULL DEFAULT (NULL), [birthState] [varchar](2) NULL DEFAULT (NULL), [birthCity] [varchar](50) NULL DEFAULT (NULL), [deathYear] [int] NULL DEFAULT (NULL), [deathMonth] [int] NULL DEFAULT (NULL), [deathDay] [int] NULL DEFAULT (NULL), [deathCountry] [varchar](50) NULL DEFAULT (NULL), [deathState] [varchar](2) NULL DEFAULT (NULL), [deathCity] [varchar](50) NULL DEFAULT (NULL), [nameFirst] [varchar](50) NULL DEFAULT (NULL), [nameLast] [varchar](50) NULL DEFAULT (NULL), [nameNote] [varchar](255) NULL DEFAULT (NULL), [nameGiven] [varchar](255) NULL DEFAULT (NULL), [nameNick] [varchar ](255) NULL ПО подразбиране (NULL), [тегло] [int] NULL ПО ПОДРАЗБИРАНЕ (NULL), [височина] [int] NULL, [прилепи] [varchar](1) NULL ПО подразбиране (NULL), [хвърля] [varchar](1) NULL DEFAULT (NULL), [дебют] [varchar]( 10) NULL DEFAULT (NULL), [finalGame] [varchar](10) NULL DEFAULT (NULL), [колеж] [varchar](50) NULL DEFAULT (NULL), [lahman40ID] [varchar](9) NULL DEFAULT ( NULL), [lahman45ID] [varchar](9) NULL DEFAULT (NULL), [retroID] [varchar](9) NULL DEFAULT (NULL), [holtzID] [varchar](9) NULL DEFAULT (NULL), [bbrefID ] [varchar](9) NULL ПО ПОДРАЗБИРАНЕ (NULL), ПЪРВИЧЕН КЛУСТРИР ([lahmanID] ASC) НА [PRIMARY]) НА [PRIMARY]; ВЪЗВЕТЕ ВМЕСТЕ В [dbo].[PlayerInfo] ([lahmanID] ,[playerID] ,[managerID] ,[hofID] ,[birthYear] ,[birthMonth] ,[birthDay] ,[birthCountry] ,[birthState] ,[birthCity] ,[deathYear] ,[deathMonth] ,[deathDay]] ,[death[Country] състояние на смъртта] , [deathCity] , [nameFirst] , [nameLast] ,[nameNote] ,[nameGiven] ,[nameNick] ,[тегло] ,[височина] ,[прилепи] ,[хвърляне] ,[дебют] ,[finalGame] ,[колеж] ,[lahman40ID] ,[lahman45ID] ,[ retroID] ,[holtzID] ,[bbrefID])SELECT [lahmanID] ,[playerID] ,[managerID] ,[hofID] ,[birthYear] ,[birthMonth] ,[birthDay] ,[birthCountry] ,[birthState] ,[birthCity ] , [deathYear] , [deathMonth] , [deathDay] , [deathCountry] , [deathState] , [deathCity] , [nameFirst] , [nameLast] , [nameNote] , [nameGiven] , [nameNick] , [тегло] , [височина] ,[прилепи] ,[хвърляне] ,[дебют] ,[finalGame] ,[колеж] ,[lahman40ID] ,[lahman45ID] ,[retroI D] ,[holtzID] ,[bbrefID]FROM [dbo].[Играчи]WHERE [lahmanID] <=10000; СЪЗДАЙТЕ ИЗГЛЕД [PlayerPostSeason]С ИЗБИРАНЕ НА SCHEMABINDINGAS [p].[lahmanID], [p].[nameFirst], [p].[nameLast], [p].[дебют], [p].[finalGame], [pp]. ].[yearID], [pp].[round], [pp].[teamID], [pp].[W], [pp].[L], [pp].[G] FROM [dbo]. [PlayerInfo] [p] JOIN [dbo].[PitchingPost] [pp] ON [p].[playerID] =[pp].[playerID]; СЪЗДАЙТЕ УНИКАЛЕН КЛУСТРИРАН ИНДЕКС [CI_PlayerPostSeason] НА [PlayerPostSeason] ([lahmanID], [yearID], [round]); СЪЗДАЙТЕ НЕКЛУСТРИРАН ИНДЕКС [NCI_PlayerPostSeason_Name] НА [PlayerPostSeason] ([nameFirst], [nameLast]);

Ако проверим статистически данни за клъстерираните и неклъстерирани индекси, ще видим, че съществуват:

DBCC SHOW_STATISTICS ('PlayerPostSeason', CI_PlayerPostSeason) WITH STAT_HEADER;GODBCC SHOW_STATISTICS ('PlayerPostSeason', NCI_PlayerPostSeason_Name) WITH STAT_preHeADER;GO; 

Статистически данни за изглед на индекс след първоначално създаване

Сега ще вмъкнем още редове в PlayerInfo:

INSERT INTO [dbo].[PlayerInfo] ([lahmanID] ,[playerID] ,[managerID] ,[hofID] ,[birthYear] ,[birthMonth] ,[birthDay)] ,[birthCountry] ,[birthState] ,[ Град на раждане] , [DeathYear] , [deathMonth] , [deathDay] , [deathCountry] , [deathState] , [deathCity] , [nameFirst] , [nameLast] , [nameNote] , [nameGiven] , [nameNick] , [weight ,[височина] ,[прилепи] ,[хвърляне] ,[дебют] ,[finalGame] ,[колеж] ,[lahman40ID] ,[lahman45ID] ,[retroID] ,[holtzID] ,[bbrefID])SELECT [lahmanID] , [playerID] , [managerID] , [hofID] , [birthYear] ,[birthMonth] ,[birthDay] ,[birthCountry] ,[birthState] ,[birthCity] , [deathYear] , [deathMonth] , [deathDay] , [deathCountry] , [deathState] , [deathCity] , [nameFirst] , [nameLast] , [nameNote] , [nameGiven] , [nameNick] , [тегло] , височина] , [прилепи] , [хвърляне] , [дебют] , [finalGame] , [колеж] , [lahman40ID] , [lahman45ID] , [retroID] , [holtzID] , [bbrefID]FROM [dbo].[Играчи] WHERE [lahmanID]> 10000;

И ако проверим sys.dm_db_stats_properties, можем да видим модификациите на реда:

SELECT [sch].[name] AS [Схема], [so].[name] AS [ObjectName], [so].[type] AS [ObjectType], [ss].[name] AS [Статистика] ], [sp].[last_updated] AS [StatsLastUpdated] , [sp].[редове] AS [RowsInTable] , [sp].[rows_sampled] AS [RowsSampled] , [sp].[modification_counter] AS [RowModifications]FROM [sys].[objects] [so]JOIN [sys].[stats] [ss] ON [so].[object_id] =[ss].[object_id]JOIN [sys].[schemas] [sch] ON [Sys].[object_id] so].[schema_id] =[sch].[schema_id]ВЪНШНО ПРИЛОЖЕНИЕ [sys].[dm_db_stats_properties]([so].[object_id], [ss].[stats_id]) spWHERE [so].[name] =' PlayerPostSeason';

Редове, променени в индексирания изглед, чрез sys.dm_db_stats_properties

И само за забавление, ако проверим sys.sysindexes, можем да видим и модификациите там:

SELECT [so].[name], [si].[name], [si].[rowcnt], [si].[rowmodctr]FROM [sys].[sysindexes] [si]JOIN [sys] .[objects] [so] ON [si].[id] =[so].[object_id]WHERE [so].[name] ='PlayerPostSeason';

Редове, променени в индексирания изглед, чрез sys.sysindexes

Сега sys.sysindexes е остарял, но ако си спомняте от предишната ми публикация, това е, което sp_updatestats използва, за да види какво е променено. Но... списъкът с обекти за sys.indexes се управлява от заявката срещу sys.objects, която, ако си спомняте, филтрира по потребителски таблици ('U') и вътрешни таблици ('IT'). Той не включва изгледи („V“) в този филтър. Като такъв, когато стартираме sp_updatestats и проверяваме изхода (не е включен за краткост), няма споменаване на нашия изглед PlayerPostSeason.

Следователно, ако имате индексирани изгледи и разчитате на sp_updatestats, за да актуализирате статистиката си, статистическите ви изгледи не се актуализират. Предполагам обаче, че повечето от вас имат активирана опцията Auto Update Statistics за вашите бази данни. Това е добре, защото с тази опция статистиката за преглед ще се актуализира, ако е била анулирана. Знаем, че сме направили над 2000 модификации на индексите на PlayerPostSeason. Ако правим заявка с селективно име, нашият план за заявка трябва да използва индекса NCI_PlayerPostSeason_Name и тъй като статистическите данни са остарели, те трябва да бъдат актуализирани. Нека проверим:

SELECT *FROM [PlayerPostSeason]WHERE [nameFirst] ='Madison';GO

План за заявка от SELECT срещу неклъстериран индекс

В плана можем да видим, че е използван неклъстерираният индекс NCI_PlayerPostSeason_Name и ако проверим статистиката:

Статистика след автоматична актуализация

Разбира се, статистическите данни за неклъстерирания индекс са актуализирани. Но, разбира се, не искаме да разчитаме на автоматично актуализиране, за да управляваме статистиката, искаме да сме проактивни. Имаме две опции:

  • Задача за поддръжка
  • Персонализиран скрипт

Задачата за поддръжка на статистически данни за актуализиране прави актуализиране на статистиката за прегледи. Това не се извиква специално никъде в потребителския интерфейс, но ако създадем план за поддръжка със задачата за актуализиране на статистиката и го изпълним, статистическите данни за индексирания изглед се актуализират. Недостатъкът на задачата за поддръжка на актуална статистика е, че това е подход с чук. Актуализира всички статистика, независимо дали е необходима (това е почти толкова лошо, колкото sp_updatestats). Предпочитам персонализиран скрипт, при който SQL Server актуализира само това, което е променено. Ако не обичате да създавате собствен сценарий, можете да използвате сценария на Ола Халенгрен. Обичайно е да актуализирате статистическите данни като част от възстановяването и реорганизацията на вашия индекс. Например, със скрипта на Ola в заданието на SQL агент ще имате:

sqlcmd -E -S $(ESCAPE_SQUOTE(SRVR)) -d master -Q "ИЗПЪЛНЯВАНЕ [dbo].[IndexOptimize] @Databases ='BaseballData', @FragmentationLow =NULL, @FragmentationMedium ='INDEX_REFORGANIZE_INDEX_REFORGANIZE',igh =REFORGANIZE_INDEX_REFORGANIZE_ ', @FragmentationLevel1 =5, @FragmentationLevel2 =30, @UpdateStatistics ='ВСИЧКИ', @OnlyModifiedStatistics ='Y', @LogToTable ='Y'" –b

С тази опция, ако статистическите данни са били променени, те ще бъдат актуализирани и ако проверим съхранената процедура [dbo].[IndexOptimize], можем да видим къде Ola проверява за модификации:

 -- Променени ли са данните в статистиката след последното актуализиране на статистиката? АКО @CurrentStatisticsID НЕ Е NULL И @UpdateStatistics НЕ Е NULL И @OnlyModifiedStatistics ='Y' BEGIN SET @CurrentCommand10 ='' АКО @LockTimeout НЕ Е NULL SET @CurrentCommand10 ='SET LOCK_TIMEOUT(@nLock TIMEOUT) +1LockTimeout(1LockTimeout) + '; ' АКО (@Version>=10.504000 И @Version <11) ИЛИ @Version>=11.03000 BEGIN SET @CurrentCommand10 =@CurrentCommand10 + 'USE ' + QUOTENAME(@CurrentDatabaseName) + АКО СЪЩЕСТВУВА(SELECT * FROM sys.dm_db_stats_properties (@ParamObjectID, @ParamStatisticsID) ​​WHERE modification_counter> 0) BEGIN SET @ParamStatisticsModified =1 END' END' END ELSE BEGIN SET @CurrentCommand10 =@CurrentCommand10 =@Current'IF'NAME + FEXISTTESNAME @CurrentDatabaseName) + '.sys.sysindexes sysindexes WHERE sysindexes.[id] =@ParamObjectID И sysindexes.[indid] =@ParamStatisticsID И sysindexes.[rowmodctr] <> 0) BEGIN SET @ParamStatisticsModified END 

За версии, които поддържат sys.dm_db_stats_properties DMF, Ola го проверява за всякакви статистически данни, които са били променени, а за версии, които не поддържат новия sys.dm_db_stats_properties DMF, се проверява системната таблица sys.sysindexes. Единственото ми оплакване тук е, че скриптът се държи по същия начин като sp_updatestats:ако поне един ред е променен, статистиката ще бъде актуализирана.

Ако не обичате да пишете свой собствен код за управление на статистиката, тогава бих препоръчал да се придържате към скрипта на Ola. Но ако искате да насочите актуализациите си малко повече, тогава бих препоръчал да използвате sys.dm_db_stats_properties. Този DMF е достъпен само за SQL Server 2008R2 SP2 и по-нова версия и SQL Server 2012 SP1 и по-нова версия, така че ако използвате по-ниска версия, ще трябва да използвате sys.indexes. Но за тези от вас с достъп до sys.dm_db_stats_properties, ето една заявка, за да започнете:

SELECT [sch].[name] AS [Схема], [so].[name] AS [ObjectName], [so].[type] AS [ObjectType], [ss].[name] AS [Статистика] ], [sp].[last_updated] AS [StatsLastUpdated] , [sp].[редове] AS [RowsInTable] , [sp].[rows_sampled] AS [RowsSampled] , CAST(100 * [sp].[rows_sampled] / [sp].[редове] AS DECIMAL (18, 2)) AS [PercentSampled], [sp].[modification_counter] AS [RowModifications] , CAST(100 * [sp].[modification_counter] / [sp].[редове]. ] КАТО DECIMAL(18, 2)) AS [PercentChange]FROM [sys].[objects] AS [so]INNER JOIN [sys].[stats] AS [ss] ON [so].[object_id] =[ss] .[object_id]INNER JOIN [sys].[schemas] AS [sch] ON [so].[schema_id] =[sch].[schema_id]OUTER APPLY [sys].[dm_db_stats_properties]([so].[object_id] , [ss].[stats_id]) AS [sp]WHERE [so].[type] IN ('U','V')AND ((CAST(100 * [sp].[modification_counter] / [sp].) [редове] КАТО DECIMAL(18,2))>=10.0))ПОРЪЧКА ПО ИЗПЪЛНЕНИЕ(100 * [sp].[modification_counter] / [sp].[редове] КАТО DECIMAL(18, 2)) DESC; 

Имайте предвид, че с sys.objects ние филтрираме по таблици и изгледи; можете да промените това, за да включите системни таблици. След това можете да модифицирате предиката, за да извлича само редове въз основа на процента на модифицираните редове или може би комбинация от процент на модификация и брой редове (за таблици с милиони или милиарди редове този процент може да е по-нисък, отколкото за малки таблици).

Резюме

Съобщението за вкъщи тук е доста ясно:не препоръчвам да използвате sp_updatestats за управление на статистиката. Статистическите данни се актуализират, когато един или повече редове се променят (което е изключително нисък праг за актуализиране на статистиката) и статистиката за индексирани изгледи не актуализиран. Това не е изчерпателен и ефективен метод за управление на статистически данни... и задачата за актуализиране на статистиката в план за поддръжка не е много по-добра. Той актуализира статистическите данни за индексираните изгледи, но актуализира всеки статистика, независимо от модификациите. Персонализираният скрипт наистина е правилният начин, но имайте предвид, че скриптът на Ола Халенгрен, ако актуализирате въз основа на модификация, също се актуализира, когато е променен само ред (но поне получава индексираните изгледи). В крайна сметка, за най-добър контрол, потърсете свой собствен скрипт за управление на статистиката. Дадох ви основната заявка, за да започнете. Ако можете да блокирате няколко часа, за да практикувате писането на T-SQL и след това да го тествате, ще имате работещ персонализиран скрипт, готов за вашите бази данни, преди да настъпят празниците.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. DSN файлове и IRI софтуер

  2. Каква е употребата на функцията DECODE в SQL?

  3. Силни референтни курсори с тип данни на запис, базиран на таблица

  4. Открояване на удари в пълнотекстово търсене

  5. Как да извлечете подниз от низ в T-SQL