Статистика зникає після поступового оновлення


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!!!!!!'

Як показано, ми не можемо відновити індекси за допомогою розділу в Інтернеті, не втрачаючи всі статистичні дані щодо індексу. Це головне питання технічного обслуговування для нас. Майже виявляється, що додаткова опція статистики повинна бути частиною синтаксису відновлення єдиного індексу або онлайновий варіант повинен належним чином обробляти його, як це робить варіант офлайн.

Будь ласка, дайте мені знати, якщо я щось пропускаю?

Оновлення:

Щодо нашої потреби в додатковій статистиці: ми розділяємо внутрішній ідентифікатор клієнта, а не дату. Отже, коли залучається новий клієнт (велика зворотна завантаженість даних), ми можемо просто оновити статистику для розділу та швидко уникнути створення неприємних планів для цього нового клієнта. Думаю, я подам його в Microsoft як помилку і побачу, що вони мають сказати, і вирішу лише повторний вибір вибір статистики для цього розділу.

Підключення звіту про помилку:

Статистика зникає після відновлення індексу в Інтернеті з додатковою статистикою

Оновлення: Microsoft підтвердила, що це помилка.


1
Оновлення: Microsoft сьогодні надіслав мені електронний лист про те, що ця помилка буде виправлена ​​під час наступного оновлення МС для SQL 2014.
JasonR

чи знаєте ви, які КУ виправили це, або що таке КБ, про що вони повідомили в цьому електронному листі? Спробуємо побачити, коли це було виправлено.
mbourgon

1
Досить впевнений, що це помилка VSTS номер 8046729 KB Номер артикулу 3194959, яка входила до складу 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без вказаної частоти вибірки:

  • Створіть непосильну статистику за допомогою програми Fullscan: час процесора = 23500 мс, минулий час = 22521 мс.
  • Створіть поступову статистику WIth Fullscan: час процесора = 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 за допомогою Resample (10% - наша визначена частота вибірки): час процесора = 63 мс, минулий час = 63 мс.

Ось де нам потрібно прийняти рішення. Чи візьмемо виграш 63 мс. оновлення статистики на основі розділів, чи ми збільшуємо показник вибірки ще вище? Скажімо, ми готові прийняти початковий показник вибірки на рівні 50% на основі додаткової статистики:

  • Оновіть додаткову статистику на 50%: минув час = 16840 мс.
  • Оновіть додаткову статистику, розділ 3 за допомогою Resample (50% - наш новий час оновлення): минуло час = 295 мс.

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

Хоча одна остання цікава річ. Що з оновленнями синхронної статистики? Чи збережена 50% частота вибірки, навіть коли починається автозапуск?

Я видалив дані з розділу 3 і запустив запит на CreationDate і перевірив, а потім перевірив ставки з тим самим запитом, що вказаний нижче. Збережено 50% вибірки.

Отже, короткий короткий сюжет: додаткова статистика може бути корисним інструментом при правильному обмірі думок та початкових роботах із налаштування. Однак ви повинні знати проблему, яку ви намагаєтеся вирішити, і тоді вам потрібно її вирішити належним чином. Якщо ви отримуєте погані оцінки потужності, ви могли б бути в змозі отримати кращі плани з стратегічної частотою дискретизації і деякі інвестованого втручання. Однак ви отримуєте лише невелику частину переваги, оскільки використовувана гістограма являє собою єдину, об’єднану сторінку статистики, а не інформацію про рівень розділу. Якщо ви відчуваєте біль у вікні технічного обслуговування, то, можливо, додаткова статистика може вам допомогти, але, ймовірно, вам знадобиться налаштувати процес втручання технічного обслуговування з високим дотиком. Незалежно,:

  • Статистика, створена за допомогою індексів, не узгоджених розділами з базовою таблицею.
  • Статистика, створена на читаються вторинні бази даних AlwaysOn.
  • Статистика, створена на базі даних лише для читання.
  • Статистика створена за відфільтрованими індексами.
  • Статистика створена за переглядами.
  • Статистика, створена на внутрішніх таблицях.
  • Статистика, створена за допомогою просторових індексів або 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)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.