Статистика исчезает после постепенного обновления

21

У нас есть большая многораздельная база данных SQL Server, использующая добавочную статистику. Все индексы секционированы выровнены. Когда мы пытаемся перестроить раздел онлайн по разделам, вся статистика исчезает после перестроения индекса.

Ниже приведен сценарий для репликации проблемы в SQL Server 2014 с базой данных AdventureWorks2014.

--Example against AdventureWorks2014 Database

CREATE PARTITION FUNCTION TransactionRangePF1 (DATETIME)
AS RANGE RIGHT FOR VALUES 
(
   '20130501', '20130601', '20130701', '20130801', 
   '20130901', '20131001', '20131101', '20131201', 
   '20140101', '20140201', '20140301'
);
GO

CREATE PARTITION SCHEME TransactionsPS1 AS PARTITION TransactionRangePF1 TO 
(
  [PRIMARY], [PRIMARY], [PRIMARY], [PRIMARY], [PRIMARY], 
  [PRIMARY], [PRIMARY], [PRIMARY], [PRIMARY], [PRIMARY], 
  [PRIMARY], [PRIMARY], [PRIMARY]
);
GO

CREATE TABLE dbo.TransactionHistory 
(
  TransactionID        INT      NOT NULL, -- not bothering with IDENTITY here
  ProductID            INT      NOT NULL,
  ReferenceOrderID     INT      NOT NULL,
  ReferenceOrderLineID INT      NOT NULL DEFAULT (0),
  TransactionDate      DATETIME NOT NULL DEFAULT (GETDATE()),
  TransactionType      NCHAR(1) NOT NULL,
  Quantity             INT      NOT NULL,
  ActualCost           MONEY    NOT NULL,
  ModifiedDate         DATETIME NOT NULL DEFAULT (GETDATE()),
  CONSTRAINT CK_TransactionType 
    CHECK (UPPER(TransactionType) IN (N'W', N'S', N'P'))
) 
ON TransactionsPS1 (TransactionDate);


INSERT INTO dbo.TransactionHistory
SELECT * FROM Production.TransactionHistory
--  SELECT * FROM sys.partitions
--  WHERE object_id = OBJECT_ID('dbo.TransactionHistory');

CREATE NONCLUSTERED INDEX IDX_ProductId ON dbo.TransactionHistory (ProductId) 
  WITH (DATA_COMPRESSION = ROW, STATISTICS_INCREMENTAL=ON)  
  ON TransactionsPS1 (TransactionDate)

DBCC SHOW_STATISTICS('dbo.TransactionHistory', IDX_ProductId);
PRINT 'Stats are avialable'  

ALTER INDEX [IDX_ProductId] ON [dbo].[TransactionHistory] REBUILD 
  PARTITION = 9 WITH (ONLINE = ON , DATA_COMPRESSION = ROW)

PRINT 'After online index rebuild by partition stats are now gone'
DBCC SHOW_STATISTICS('dbo.TransactionHistory', IDX_ProductId);

PRINT 'Rebuild the stats with a rebuild for all paritions (this works)' 
ALTER INDEX [IDX_ProductId] ON [dbo].[TransactionHistory] REBUILD 
  PARTITION = ALL WITH (ONLINE = ON , DATA_COMPRESSION = ROW, 
  STATISTICS_INCREMENTAL = ON)

PRINT 'Stats are back'
DBCC SHOW_STATISTICS('dbo.TransactionHistory', IDX_ProductId);

PRINT 'Works correctly for an offline rebuild by partition'
ALTER INDEX [IDX_ProductId] ON [dbo].[TransactionHistory] REBUILD 
  PARTITION = 9 WITH (ONLINE = OFF , DATA_COMPRESSION = ROW)

    --stats still there  
DBCC SHOW_STATISTICS('dbo.TransactionHistory', IDX_ProductId);

ALTER INDEX [IDX_ProductId] ON [dbo].[TransactionHistory] REBUILD 
  PARTITION = 9 WITH (ONLINE = ON , DATA_COMPRESSION = ROW)

DBCC SHOW_STATISTICS('dbo.TransactionHistory', IDX_ProductId);
PRINT' stats are gone!!!!!!'

Как показано, мы не можем перестроить индексы по разделам онлайн без потери всей статистики для индекса. Это главная проблема для нас. Похоже, что инкрементный параметр stats должен быть частью синтаксиса перестройки единого индекса или онлайн-параметр должен правильно обрабатывать его, как это делает параметр offline.

Пожалуйста, дайте мне знать, если я что-то упустил?

Обновления:

Что касается нашей потребности в дополнительной статистике: мы разбиваем внутренний идентификатор клиента, а не дату. Поэтому, когда вводится новый клиент (большая загрузка данных), мы можем просто обновить статистику для раздела и быстро избежать любых уродливых планов, созданных для этого нового клиента. Я думаю, что подам это в Microsoft в качестве ошибки и посмотрим, что они скажут, и воспользуюсь решением простой повторной выборки статистики для этого раздела.

Отчет об ошибке подключения:

Статистика исчезает после перестроения индекса онлайн с добавочной статистикой

Обновление: Microsoft подтвердила, что это ошибка.

JasonR
источник
1
Обновление: Microsoft отправила мне электронное письмо этим утром, что эта ошибка будет исправлена ​​в следующем обновлении CU для SQL 2014.
JasonR
Знаете ли вы, какие CU исправили это, или что такое KB, о которых они сообщили в этом письме? Попытка увидеть, когда это было исправлено.
mbourgon
1
Я уверен, что это ошибка VSTS № 8046729 КБ Номер статьи 3194959, которая была частью CU 9 для SQL Server 2014 SP1. Ссылка на КБ находится здесь .
JasonR
Да, это выглядит так - и было только что исправлено на 2016SP1 в прошлом месяце. Огромное спасибо!
mbourgon
Исправление: только что исправлено в 2016 SP1 CU2. Это происходит на 2016 SP1 CU1.
mbourgon

Ответы:

17

Не уверен, что это ошибка, сама по себе, но это определенно интересное явление. Оперативные перестройки разделов являются новыми в SQL Server 2014, поэтому с этим можно разобраться.

Вот мое лучшее объяснение для вас. Инкрементная статистика абсолютно требует, чтобы все разделы отбирались с одинаковой частотой, чтобы при объединении движка страниц статистики можно было с уверенностью сопоставить выборочное распределение. REBUILDобязательно выборки данных с частотой выборки 100%. Нет никакой гарантии, что частота выборки 100% в разделе 9 всегда будет точной частотой дискретизации остальных разделов. Из-за этого создается впечатление, что движок не может объединить сэмплы, и в результате вы получаете пустой блок статистики. Тем не менее, объект статистики все еще там:

select 
    check_time = sysdatetime(),                         
    schema_name = sh.name,
    table_name = t.name,
    stat_name = s.name,
    index_name = i.name,
    stats_column = index_col(quotename(sh.name)+'.'+quotename(t.name),s.stats_id,1),
    s.stats_id,
    s.has_filter,                       
    s.is_incremental,
    s.auto_created,
    sp.last_updated,    
    sp.rows,
    sp.rows_sampled,                        
    sp.unfiltered_rows,
    modification_counter 
from sys.stats s 
join sys.tables t 
    on s.object_id = t.object_id
join sys.schemas sh
    on t.schema_id = sh.schema_id
left join sys.indexes i 
    on s.object_id = i.object_id
    and s.name = i.name
outer apply sys.dm_db_stats_properties(s.object_id, s.stats_id) sp
where t.name = 'TransactionHistory' and sh.name = 'dbo'

Вы можете заполнить блоб любым количеством способов:

UPDATE STATISTICS dbo.TransactionHistory (IDX_ProductId) WITH RESAMPLE;

или

UPDATE STATISTICS dbo.TransactionHistory (IDX_ProductId) WITH RESAMPLE ON PARTITIONS (9);

или вы можете дождаться обновления AutoStats при первой компиляции плана запроса с использованием этого объекта:

-- look at my creative query
select * 
from dbo.TransactionHistory
where TransactionDate = '20140101';

Сказав все это, этот просветляющий пост Эрин Стеллато подчеркивает то, что стало восприниматься как серьезный недостаток дополнительных статистических данных. Их данные уровня раздела не используются оптимизатором при генерации плана запроса, что снижает предполагаемое преимущество добавочной статистики. Какова же тогда текущая выгода от добавочной статистики? Я бы сказал, что их основная полезность заключается в возможности более последовательно выбирать большие таблицы с большей скоростью, чем при традиционной статистике.

Используя ваш пример, вот как все выглядит:

set statistics time on;

update statistics dbo.TransactionHistory(IDX_ProductId)
with fullscan;

--SQL Server Execution Times:
--  CPU time = 94 ms,  elapsed time = 131 ms.


update statistics dbo.TransactionHistory(IDX_ProductId)
with resample on partitions(2);

 --SQL Server Execution Times:
 --  CPU time = 0 ms,  elapsed time = 5 ms.

drop index IDX_ProductId On dbo.TransactionHistory;

CREATE NONCLUSTERED INDEX IDX_ProductId ON dbo.TransactionHistory (ProductId) 
  WITH (DATA_COMPRESSION = ROW)  
  ON [PRIMARY]

update statistics dbo.TransactionHistory(IDX_ProductId)
with fullscan;

 --SQL Server Execution Times:
 --  CPU time = 76 ms,  elapsed time = 66 ms.

Обновление статистики полного сканирования дополнительной статистики стоит 131 мс. Обновление статистики полной развертки для статистики без выравнивания по разделам стоит 66 мс. Несогласованная статистика, скорее всего, медленнее из-за накладных расходов, связанных с объединением отдельных страниц статистики обратно в основную гистограмму., Однако, используя выровненный по разделам статистический объект, мы можем обновить один раздел и объединить его обратно в основной блок гистограммы за 5 мс. Поэтому в этот момент администратор с добавочной статистикой сталкивается с решением. Они могут уменьшить общее время обслуживания своей статистики, обновляя традиционно только разделы, или они могут экспериментировать с более высокими частотами выборки, так что они могут получить больше строк, выбранных за тот же период времени, что и их предыдущий период обслуживания. Первый позволяет получить передышку в окне обслуживания, а второй может перенести статистику по очень большой таблице в место, где запросы получают более точные планы на основе более точной статистики. Это не гарантия, и ваш пробег может отличаться.

Читатель может видеть, что 66 мс - это не болезненное время обновления статистики для этой таблицы, поэтому я попытался настроить тест для набора данных stackexchange. В недавнем дампе, который я скачал, содержится 6 418 608 сообщений (исключая сообщения StackOverflow и все сообщения с 2012 года - ошибка данных с моей стороны).

Я разделил данные, [CreationDate]потому что ... демо.

Вот некоторые моменты времени для некоторых довольно стандартных сценариев (100% - перестроение индекса, по умолчанию - автоматическое обновление статистики или UPDATE STATISTICSбез указанной частоты выборки:

  • Создать неинкрементную статистику с полным сканированием: время ЦП = 23500 мс, прошедшее время = 22521 мс.
  • Создание инкрементной статистики с полным сканированием: время ЦП = 20406 мс, прошедшее время = 15413 мс.
  • Обновить неинкрементную статистику с частотой дискретизации по умолчанию: время ЦП = 406 мс, прошедшее время = 408 мс.
  • Обновление инкрементной статистики с частотой дискретизации по умолчанию: время ЦП = 453 мс, прошедшее время = 507 мс.

Допустим, мы более изощренны, чем эти сценарии по умолчанию, и решили, что частота выборки 10% - это минимальная частота, которая должна дать нам планы, которые нам нужны, при сохранении времени обслуживания на разумные сроки.

  • Обновить неинкрементную статистику с выборкой 10 процентов: время ЦП = 2344 мс, прошедшее время = 2441 мс.
  • Обновление инкрементной статистики с выборкой 10 процентов: время ЦП = 2344 мс, прошедшее время = 2388 мс.

Пока что нет никакой явной пользы в том, чтобы иметь возрастающую статистику. Однако, если мы используем недокументированную sys.dm_db_stats_properties_internal() DMV (ниже), вы можете получить представление о том, какие разделы вы хотите обновить. Допустим, мы внесли изменения в данные в разделе 3 и хотим, чтобы статистика была свежей для входящих запросов. Вот наши варианты:

  • Обновление неинкрементное по умолчанию (также поведение по умолчанию при автоматическом обновлении статистики): 408 мс.
  • Обновление без инкремента при 10%: 2441 мс.
  • Обновите инкрементную статистику, раздел 3 с повторной выборкой (10% - наша определенная частота выборки): время ЦП = 63 мс, истекшее время = 63 мс.

Вот где нам нужно принять решение. Берём ли мы победу в 63 мс. обновление статистики на основе разделов, или мы увеличим частоту дискретизации еще выше? Допустим, мы готовы принять начальный показатель выборки на уровне 50% для инкрементальной статистики:

  • Обновление инкрементной статистики на 50%: истекшее время = 16840 мс.
  • Обновление инкрементной статистики, раздел 3 с повторной выборкой (50% - наше новое время обновления): истекшее время = 295 мс.

Мы можем выбирать намного больше данных, возможно, настроив оптимизатор, чтобы лучше угадывать наши данные (хотя он пока не использует статистику на уровне разделов), и мы можем сделать это быстрее, теперь, когда у нас есть инкрементная статистика.

Впрочем, еще одна забавная вещь. А как насчет синхронных обновлений статистики? Сохраняется ли частота выборки 50% даже при включении автостата?

Я удалил данные из раздела 3, запустил запрос на CreationDate и проверил, а затем проверил ставки с помощью того же запроса, приведенного ниже. Частота дискретизации 50% была сохранена.

Итак, короче говоря: инкрементная статистика может быть полезным инструментом с достаточным количеством мыслей и начальной настройкой. Тем не менее, вы должны знать проблему, которую вы пытаетесь решить, а затем вам нужно решить ее соответствующим образом. Если вы получаете плохие оценки мощности, вы можете получить лучшие планы со стратегической частотой выборки и некоторым вложенным вмешательством. Однако вы получаете только небольшую часть выгоды, поскольку используемая гистограмма представляет собой единую объединенную страницу статистики, а не информацию об уровне раздела. Если вы чувствуете боль в окне технического обслуживания, то, возможно, вам может помочь инкрементная статистика, но, вероятно, вам потребуется настроить процесс вмешательства при техническом обслуживании. Несмотря на,:

  • Статистика создается с индексами, которые не выровнены по разделам с базовой таблицей.
  • Статистика создается на всегда читаемых вторичных базах данных.
  • Статистика создается на базе данных только для чтения.
  • Статистика создается по отфильтрованным индексам.
  • Статистика создается по просмотрам.
  • Статистика создается по внутренним таблицам.
  • Статистика создается с использованием пространственных индексов или индексов XML.

Надеюсь это поможет

select 
    sysdatetime(),                          
    schema_name = sh.name,
    table_name = t.name,
    stat_name = s.name,
    index_name = i.name,
    leading_column = index_col(quotename(sh.name)+'.'+quotename(t.name),s.stats_id,1),
    s.stats_id,
    parition_number = isnull(sp.partition_number,1),
    s.has_filter,                       
    s.is_incremental,
    s.auto_created,
    sp.last_updated,    
    sp.rows,
    sp.rows_sampled,                        
    sp.unfiltered_rows,
    modification_counter = coalesce(sp.modification_counter, n1.modification_counter) 
from sys.stats s 
join sys.tables t 
    on s.object_id = t.object_id
join sys.schemas sh
    on t.schema_id = sh.schema_id
left join sys.indexes i 
    on s.object_id = i.object_id
        and s.name = i.name
cross apply sys.dm_db_stats_properties_internal(s.object_id, s.stats_id) sp
outer apply sys.dm_db_stats_properties_internal(s.object_id, s.stats_id) n1
where n1.node_id = 1
    and (
            (is_incremental = 0)
               or
            (is_incremental = 1 and sp.partition_number is not null)
         )
    and t.name = 'Posts'
    and s.name like 'st_posts%'
order by s.stats_id,isnull(sp.partition_number,1)
swasheck
источник