Я збираюся обмежити цю публікацію обговоренням статистики одного стовпця, тому що вона вже буде досить тривалою, і ви зацікавлені в тому, як SQL Server з'єднує дані в кроки гістограми. Для статистики декількох стовпців гістограма створюється лише на провідному стовпці.
Коли SQL Server визначає, що необхідне оновлення статистики, він починає прихований запит, який читає або всі дані таблиці, або зразок даних таблиці. Ви можете переглядати ці запити з розширеними подіями. Існує функція, яка називається StatMan
в SQL Server, яка бере участь у створенні гістограм. Для простих об'єктів статистики існує щонайменше два різних типи StatMan
запитів (є різні запити для швидкого оновлення статистики, і я підозрюю, що функція додаткової статистики в розділених таблицях також використовує інший запит).
Перший просто збирає всі дані з таблиці без будь-якої фільтрації. Це ви можете бачити, коли таблиця дуже мала або ви збираєте статистику з FULLSCAN
опцією:
CREATE TABLE X_SHOW_ME_STATMAN (N INT);
CREATE STATISTICS X_STAT_X_SHOW_ME_STATMAN ON X_SHOW_ME_STATMAN (N);
-- after gathering stats with 1 row in table
SELECT StatMan([SC0]) FROM
(
SELECT TOP 100 PERCENT [N] AS [SC0]
FROM [dbo].[X_SHOW_ME_STATMAN] WITH (READUNCOMMITTED)
ORDER BY [SC0]
) AS _MS_UPDSTATS_TBL
OPTION (MAXDOP 16);
SQL Server вибирає автоматичний розмір вибірки залежно від розміру таблиці (я думаю, що це і кількість рядків, і сторінок у таблиці). Якщо таблиця занадто велика, то розмір автоматичного зразка падає нижче 100%. Ось що я отримав для тієї ж таблиці з 1М рядками:
-- after gathering stats with 1 M rows in table
SELECT StatMan([SC0], [SB0000]) FROM
(
SELECT TOP 100 PERCENT [SC0], step_direction([SC0]) over (order by NULL) AS [SB0000]
FROM
(
SELECT [N] AS [SC0]
FROM [dbo].[X_SHOW_ME_STATMAN] TABLESAMPLE SYSTEM (6.666667e+001 PERCENT) WITH (READUNCOMMITTED)
) AS _MS_UPDSTATS_TBL_HELPER
ORDER BY [SC0], [SB0000]
) AS _MS_UPDSTATS_TBL
OPTION (MAXDOP 1);
TABLESAMPLE
є документально підтвердженим, але StatMan та step_direction - ні. тут SQL Server відбирає близько 66,6% даних із таблиці для створення гістограми. Це означає, що ви могли отримати різну кількість кроків гістограми, оновлюючи статистику (без FULLSCAN
) на одних і тих же даних. Я ніколи цього не спостерігав на практиці, але не бачу, чому це було б неможливо.
Давайте проведемо кілька тестів на простих даних, щоб побачити, як статистика змінюється з часом. Нижче наведено тестовий код, який я написав, щоб вставити послідовні цілі числа в таблицю, зібрати статистику після кожного вставки та зберегти інформацію про статистику в таблицю результатів. Почнемо з просто вставлення 1 рядки до 10000. Тестовий шар:
DECLARE
@stats_id INT,
@table_object_id INT,
@rows_per_loop INT = 1,
@num_of_loops INT = 10000,
@loop_num INT;
BEGIN
SET NOCOUNT ON;
TRUNCATE TABLE X_STATS_RESULTS;
SET @table_object_id = OBJECT_ID ('X_SEQ_NUM');
SELECT @stats_id = stats_id FROM sys.stats
WHERE OBJECT_ID = @table_object_id
AND name = 'X_STATS_SEQ_INT_FULL';
SET @loop_num = 0;
WHILE @loop_num < @num_of_loops
BEGIN
SET @loop_num = @loop_num + 1;
INSERT INTO X_SEQ_NUM WITH (TABLOCK)
SELECT @rows_per_loop * (@loop_num - 1) + N FROM dbo.GetNums(@rows_per_loop);
UPDATE STATISTICS X_SEQ_NUM X_STATS_SEQ_INT_FULL WITH FULLSCAN; -- can comment out FULLSCAN as needed
INSERT INTO X_STATS_RESULTS WITH (TABLOCK)
SELECT 'X_STATS_SEQ_INT_FULL', @rows_per_loop * @loop_num, rows_sampled, steps
FROM sys.dm_db_stats_properties(@table_object_id, @stats_id);
END;
END;
За цими даними кількість кроків гістограми швидко збільшується до 200 (спочатку вона вражає максимальну кількість кроків з 397 рядків), залишається на рівні 199 або 200, поки 1485 рядків не знаходяться в таблиці, потім повільно зменшується, поки гістограма не набере лише 3 або 4 кроки. Ось графік усіх даних:
Ось гістограма виглядає як для 10-ти рядків:
RANGE_HI_KEY RANGE_ROWS EQ_ROWS DISTINCT_RANGE_ROWS AVG_RANGE_ROWS
1 0 1 0 1
9999 9997 1 9997 1
10000 0 1 0 1
Чи проблема в тому, що гістограма має лише 3 кроки? Схоже, інформація збереглася з нашої точки зору. Зауважте, що оскільки тип даних є INTEGER, ми можемо визначити, скільки рядків знаходиться в таблиці для кожного цілого числа від 1 до 10000. Зазвичай SQL Server може і це вирішити, хоча є деякі випадки, коли це не зовсім виходить. . Дивіться цей приклад SE для прикладу цього.
Як ви думаєте, що станеться, якщо ми видалимо з таблиці один рядок та оновимо статистику? В ідеалі ми отримаємо ще один крок гістограми, щоб показати, що відсутнє ціле число більше не знаходиться в таблиці.
DELETE FROM X_SEQ_NUM
WHERE X_NUM = 1000;
UPDATE STATISTICS X_SEQ_NUM X_STATS_SEQ_INT_FULL WITH FULLSCAN;
DBCC SHOW_STATISTICS ('X_SEQ_NUM', 'X_STATS_SEQ_INT_FULL'); -- still 3 steps
DELETE FROM X_SEQ_NUM
WHERE X_NUM IN (2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000);
UPDATE STATISTICS X_SEQ_NUM X_STATS_SEQ_INT_FULL WITH FULLSCAN;
DBCC SHOW_STATISTICS ('X_SEQ_NUM', 'X_STATS_SEQ_INT_FULL'); -- still 3 steps
Це трохи розчаровує. Якби ми будували гістограму вручну, ми додавали б крок для кожного відсутнього значення. SQL Server використовує алгоритм загального призначення, тому для деяких наборів даних ми можемо придумати більш підходящу гістограму, ніж код, який він використовує. Звичайно, практична різниця між отриманням 0 або 1 ряду з таблиці дуже мала. Я отримую однакові результати при тестуванні з 20000 рядків, кожне ціле число яких має 2 рядки в таблиці. Після видалення даних гістограма не отримує кроків.
RANGE_HI_KEY RANGE_ROWS EQ_ROWS DISTINCT_RANGE_ROWS AVG_RANGE_ROWS
1 0 2 0 1
9999 19994 2 9997 2
10000 0 2 0 1
Якщо я тестую 1 мільйон рядків, кожне ціле число яких має 100 рядків у таблиці, я отримую трохи кращі результати, але все одно можу скласти кращу гістограму вручну.
truncate table X_SEQ_NUM;
BEGIN TRANSACTION;
INSERT INTO X_SEQ_NUM WITH (TABLOCK)
SELECT N FROM dbo.GetNums(10000);
GO 100
COMMIT TRANSACTION;
UPDATE STATISTICS X_SEQ_NUM X_STATS_SEQ_INT_FULL WITH FULLSCAN;
DBCC SHOW_STATISTICS ('X_SEQ_NUM', 'X_STATS_SEQ_INT_FULL'); -- 4 steps
DELETE FROM X_SEQ_NUM
WHERE X_NUM = 1000;
UPDATE STATISTICS X_SEQ_NUM X_STATS_SEQ_INT_FULL WITH FULLSCAN;
DBCC SHOW_STATISTICS ('X_SEQ_NUM', 'X_STATS_SEQ_INT_FULL'); -- now 5 steps with a RANGE_HI_KEY of 998 (?)
DELETE FROM X_SEQ_NUM
WHERE X_NUM IN (2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000);
UPDATE STATISTICS X_SEQ_NUM X_STATS_SEQ_INT_FULL WITH FULLSCAN;
DBCC SHOW_STATISTICS ('X_SEQ_NUM', 'X_STATS_SEQ_INT_FULL'); -- still 5 steps
Підсумкова гістограма:
RANGE_HI_KEY RANGE_ROWS EQ_ROWS DISTINCT_RANGE_ROWS AVG_RANGE_ROWS
1 0 100 0 1
998 99600 100 996 100
3983 298100 100 2981 100
9999 600900 100 6009 100
10000 0 100 0 1
Давайте перевіримо далі з послідовними цілими числами, але з більшою кількістю рядків у таблиці. Зауважте, що для таблиць, які є занадто маленькими вручну, вказуючи розмір вибірки, це не матиме ефекту, тому я додаватиму 100 рядків у кожну вставку та збиратиму статистику щоразу до 1 мільйона рядків. Я бачу аналогічну схему, як і раніше, за винятком того, як раз я потрапляю до 637300 рядків у таблиці, я більше не відбираю 100% рядків у таблиці зі стандартною частотою вибірки. У міру отримання рядків кількість кроків гістограми збільшується. Можливо, це тому, що SQL Server закінчується більшою кількістю прогалин у даних, оскільки кількість непробованих рядків у таблиці збільшується. Я не забиваю 200 кроків навіть на 1 М рядках, але якби я продовжував додавати рядки, я сподіваюся, що дістанусь там і врешті-решт почну йти назад.
Вісь X - це кількість рядків у таблиці. Зі збільшенням кількості рядків вибіркові рядки дещо змінюються і не перевищують 650 к.
Тепер давайте зробимо кілька простих тестів з даними VARCHAR.
CREATE TABLE X_SEQ_STR (X_STR VARCHAR(5));
CREATE STATISTICS X_SEQ_STR ON X_SEQ_STR(X_STR);
Тут я вставляю 200 номерів (як рядки) разом із NULL.
INSERT INTO X_SEQ_STR
SELECT N FROM dbo.GetNums(200)
UNION ALL
SELECT NULL;
UPDATE STATISTICS X_SEQ_STR X_SEQ_STR ;
DBCC SHOW_STATISTICS ('X_SEQ_STR', 'X_SEQ_STR'); -- 111 steps, RANGE_ROWS is 0 or 1 for all steps
Зауважте, що NULL завжди отримує власний крок гістограми, коли він знайдений у таблиці. SQL Server міг би дати мені рівно 201 крок, щоб зберегти всю інформацію, але цього не зробили. Технічно інформація втрачається, оскільки "1111" сортує між "1" і "2", наприклад.
Тепер спробуємо вставити різні символи, а не просто цілі числа:
truncate table X_SEQ_STR;
INSERT INTO X_SEQ_STR
SELECT CHAR(10 + N) FROM dbo.GetNums(200)
UNION ALL
SELECT NULL;
UPDATE STATISTICS X_SEQ_STR X_SEQ_STR ;
DBCC SHOW_STATISTICS ('X_SEQ_STR', 'X_SEQ_STR'); -- 95 steps, RANGE_ROWS is 0 or 1 or 2
Ніякої реальної різниці від останнього тесту.
Тепер спробуємо вставити символи, але покладемо різні таблиці кожного символу в таблицю. Наприклад, CHAR(11)
має 1 ряд, CHAR(12)
має 2 ряди тощо.
truncate table X_SEQ_STR;
DECLARE
@loop_num INT;
BEGIN
SET NOCOUNT ON;
SET @loop_num = 0;
WHILE @loop_num < 200
BEGIN
SET @loop_num = @loop_num + 1;
INSERT INTO X_SEQ_STR WITH (TABLOCK)
SELECT CHAR(10 + @loop_num) FROM dbo.GetNums(@loop_num);
END;
END;
UPDATE STATISTICS X_SEQ_STR X_SEQ_STR ;
DBCC SHOW_STATISTICS ('X_SEQ_STR', 'X_SEQ_STR'); -- 148 steps, most with RANGE_ROWS of 0
Як і раніше, я все ще не отримую рівно 200 кроків гістограми. Однак багато кроків мають RANGE_ROWS
0.
Для остаточного тесту я збираюся вставити випадковий рядок з 5 символів у кожен цикл і збирати статистику кожного разу. Ось код випадкового рядка:
char((rand()*25 + 65))+char((rand()*25 + 65))+char((rand()*25 + 65))+char((rand()*25 + 65))+char((rand()*25 + 65))
Ось графік рядків у кроках таблиці та гістограми:
Зауважте, що кількість кроків не опускається нижче 100, коли вона починає підніматися вгору і вниз. Я десь чув (але наразі не можу отримати джерело), що алгоритм побудови гістограми SQL Server поєднує кроки гістограми, оскільки у них не вистачає місця. Таким чином, ви можете закінчити різкі зміни кількості кроків, лише додавши трохи даних. Ось один зразок даних, який мені здався цікавим:
ROWS_IN_TABLE ROWS_SAMPLED STEPS
36661 36661 133
36662 36662 143
36663 36663 143
36664 36664 141
36665 36665 138
Навіть під час вибірки з FULLSCAN
додаванням одного рядка можна збільшити кількість кроків на 10, тримати його постійним, потім зменшити на 2, потім зменшити на 3.
Що ми можемо підсумувати з усього цього? Я нічого не можу довести, але ці спостереження, мабуть, справджуються:
- SQL Server використовує алгоритм загального використання для створення гістограм. Для деяких розподілів даних можливо створити більш повне представлення даних вручну.
- Якщо в таблиці є дані NULL, а запит статистики виявляє, що дані NULL завжди отримують власний крок гістограми.
- Мінімальне значення, знайдене в таблиці, отримує власний крок гістограми з
RANGE_ROWS
= 0.
- Максимальне значення, знайдене в таблиці, буде остаточним
RANGE_HI_KEY
у таблиці.
- Оскільки SQL Server відбирає більше даних, можливо, буде потрібно комбінувати існуючі кроки, щоб звільнити місця для нових даних, які вони знаходять. Якщо ви подивитесь на достатню кількість гістограм, ви можете побачити загальні значення, які повторюються для
DISTINCT_RANGE_ROWS
або RANGE_ROWS
. Наприклад, 255 показує купу разів для RANGE_ROWS
і DISTINCT_RANGE_ROWS
для остаточного тестового випадку тут.
- Для простого розподілу даних ви можете бачити, як SQL Server поєднує послідовні дані в один етап гістограми, що не призводить до втрати інформації. Однак при додаванні прогалин до даних гістограма може не коригуватись так, як ви б сподівалися.
Коли все це проблема? Це проблема, коли запит працює погано через гістограму, яка не в змозі представити розподіл даних таким чином, щоб оптимізатор запитів прийняв хороші рішення. Я думаю, що є тенденція думати, що робити більше кроків гістограми - це завжди краще, і це може бути дивовижною ситуацією, коли SQL Server генерує гістограму на мільйони рядків і більше, але не використовує точно 200 або 201 кроків гістограми. Однак я бачив багато проблем зі статистикою, навіть коли гістограма має 200 або 201 кроки. Ми не маємо ніякого контролю над тим, скільки кроків гістограми генерує SQL Server для об’єкта статистики, тому я б не турбувався про це. Однак є кілька кроків, які ви можете зробити, коли у вас виникають погані результати запитів, викликані проблемами статистики. Я дам надзвичайно короткий огляд.
Збір статистики повністю може допомогти в деяких випадках. Для дуже великих таблиць розмір автоматичної вибірки може бути менше 1% рядків у таблиці. Іноді це може призвести до поганих планів залежно від порушення даних у стовпці. Документація Microsofts для CREATE STATISTICS та UPDATE STATISTICS говорить стільки:
Зразок корисний для особливих випадків, коли план запитів на основі вибірки за замовчуванням не є оптимальним. У більшості ситуацій не потрібно вказувати SAMPLE, оскільки оптимізатор запитів вже використовує вибірку і визначає статистично значущий розмір вибірки за замовчуванням, як це потрібно для створення високоякісних планів запитів.
Для більшості робочих навантажень повне сканування не потрібно, а вибіркова за замовчуванням є достатньою. Однак певні робочі навантаження, чутливі до широко змінюваних розподілів даних, можуть вимагати збільшення розміру вибірки або навіть повного сканування.
У деяких випадках може допомогти створення відфільтрованої статистики. У вас може бути стовпець зі скошеними даними та безліччю різних відмінних значень. Якщо в даних, які зазвичай відфільтровані, є певні значення, ви можете створити гістограму статистики лише для тих загальних значень. Оптимізатор запитів може використовувати статистику, визначену на меншому діапазоні даних, а не статистику, визначену для всіх значень стовпців. Ви все одно не гарантовано отримаєте 200 кроків у гістограмі, але якщо ви створите відфільтровану статистику лише на одному значенні, ви зробите крок гістограми на це значення.
Використання розділеного виду - це один із способів ефективно отримати більше 200 кроків для таблиці. Припустимо, що ви можете легко розділити велику таблицю на одну таблицю на рік. Ви створюєте UNION ALL
подання, яке поєднує всі щорічні таблиці. Кожна таблиця матиме власну гістограму. Зауважте, що нова додаткова статистика, представлена в SQL Server 2014, дозволяє лише оновлення статистики бути більш ефективними. Оптимізатор запитів не використовуватиме статистику, створену для кожного розділу.
Тут можна запустити ще багато тестів, тому я закликаю вас експериментувати. Я зробив це тестування на SQL Server 2014 express, тому насправді нічого не зупиняє вас.