Зміни в оцінках щодо предикатів, які містять SUBSTRING () у SQL Server 2016?


13

Чи є документація чи дослідження щодо змін у SQL Server 2016 щодо того, як оцінюється кардинальність для предикатів, що містять SUBSTRING () або інших рядкових функцій?

Причина, про яку я запитую, полягає в тому, що я дивився на запит, продуктивність якого погіршилася в режимі сумісності 130, і причина була пов'язана зі зміною оцінки кількості рядків, що відповідають умові WHERE, яка містила виклик SUBSTRING (). Я виправив проблему з перезаписом запитів, але мені цікаво, чи хтось знає про будь-яку документацію про зміни в цій області в SQL Server 2016.

Демо-код наведено нижче. Оцінки в цьому тестовому випадку дуже близькі, але точність змінюється залежно від даних.

У тестовому випадку, на рівні 120 компат, SQL Server, як здається, використовує гістограму для оцінки, тоді як у рівні компат 130 SQL Server передбачає фіксовану 10% збігів таблиці.

CREATE DATABASE MyStringTestDB;
GO
USE MyStringTestDB;
GO
DROP TABLE IF EXISTS dbo.StringTest;
CREATE TABLE dbo.StringTest ( [TheString] varchar(15) );
GO
INSERT INTO dbo.StringTest
VALUES
( 'Y5_CLV' );
INSERT INTO dbo.StringTest
VALUES
( 'Y5_EG3' );
INSERT INTO dbo.StringTest
VALUES
( 'ZY_NE' );
INSERT INTO dbo.StringTest
VALUES
( 'ZY_PQT' );
INSERT INTO dbo.StringTest
VALUES
( 'ZY_T2V' );
INSERT INTO dbo.StringTest
VALUES
( 'ZY_TT4' );
INSERT INTO dbo.StringTest
VALUES
( 'ZY_ZKK' );
INSERT INTO dbo.StringTest
VALUES
( 'ZZ_LW6' );
INSERT INTO dbo.StringTest
VALUES
( 'ZZ_QO3' );
INSERT INTO dbo.StringTest
VALUES
( 'ZZ_TZ7' );
INSERT INTO dbo.StringTest
VALUES
( 'ZZ_UZZ' );

CREATE CLUSTERED INDEX IX_Clustered ON dbo.StringTest (TheString);

/* 
Uses fixed % for estimate; 1.1 rows estimated in this case.
    Plan for computation:
        CSelCalcFixedFilter (0.1) <----
            Selectivity: 0.1
*/
ALTER DATABASE MyStringTestDB SET compatibility_level = 130;
GO
SELECT * 
FROM dbo.StringTest 
WHERE SUBSTRING(TheString, 1, CHARINDEX('_',TheString) - 1) = 'ZZ'
OPTION (QUERYTRACEON 2363, QUERYTRACEON 3604);

/* 
Uses histogram to get estimate of 1
 CSelCalcPointPredsFreqBased <----
      Distinct value calculation:
          CDVCPlanLeaf
              0 Multi-Column Stats, 1 Single-Column Stats, 0 Guesses
      Individual selectivity calculations:
          (none)
    Loaded histogram for column QCOL: [DBA].[dbo].[StringTest].TheString from stats with id 1
*/
ALTER DATABASE MyStringTestDB SET compatibility_level = 120;
GO
SELECT * 
FROM dbo.StringTest 
WHERE SUBSTRING(TheString, 1, CHARINDEX('_',TheString) - 1) = 'ZZ'
OPTION (QUERYTRACEON 2363, QUERYTRACEON 3604);

/*
-- Simpler rewrite; works fine in both compat levels and gets better estimate.
SELECT * 
FROM dbo.StringTest 
WHERE TheString LIKE 'ZZ[_]%'
OPTION (QUERYTRACEON 2363, QUERYTRACEON 3604);
*/

1
Не впевнений у конкретному питанні, але якщо Y5_EG3рядки - це лише коди і завжди великі регістри, то ви завжди можете спробувати вказати бінарне зіставлення - Latin1_General_100_BIN2- що повинно підвищити швидкість операцій фільтрації. Просто додайте COLLATE Latin1_General_100_BIN2до CREATE TABLEзаяви, відразу після varchar(15). Мені було б цікаво дізнатися, чи вплинуло це на розробку / оцінку плану.
Соломон Руцький

Відповіді:


8

Мені невідома жодна документація. Я все це розглядав і робив деякі зауваження, які занадто довгі для коментаря.

Оцінка 10% не завжди є деградацією. Візьмемо наступний приклад.

TRUNCATE TABLE dbo.StringTest

INSERT INTO dbo.StringTest
SELECT TOP (1000000) 'ZZ_' + LEFT(NEWID(), 12)
FROM   master..spt_values v1,
       master..spt_values v2;

і WHEREпункт у вашому запитанні.

WHERE SUBSTRING(TheString, 1, CHARINDEX('_',TheString) - 1) = 'ZZ'

Таблиця містить мільйон рядків. Усі вони відповідають присудку. Згідно рівня 130, 10% здогадок дає оцінку 100 000. Менше 120 оцінюваних рядків становить 1,03913.

У поведінці 120 використовується гістограма, але лише для отримання кількості чітких рядків. Вектор щільності в моєму випадку показує 1,039131E-06, і це множимо на кардинальність таблиці, щоб отримати розрахунковий кількість рядків. Усі значення насправді різні, але всі відповідають предикату.

Відстеження query_optimizer_estimate_cardinalityрозширеної події показує, що під 130 років відбувається дві різні <StatsCollection Name="CStCollFilter"події. Перший оцінює 100 000. Другий завантажує гістограму та використовує CSelCalcPointPredsFreqBased / DistinctCountCalculator, щоб отримати оцінку 1,04. Цей другий результат видається невикористаним.

Поведінка, яку ви спостерігали, не застосовується послідовно у 130. Я додав, ORDER BY TheStringочікуючи, що це буде виграш для оцінювача 130, оскільки 120 бореться з наданням пам’яті для одного ряду, але ця незначна зміна була достатньою для зменшення оцінюваних рядків до 1,03913 також у справі 130.

Додавання OPTION (QUERYRULEOFF SelectToFilter)повертає оцінку переходу в сортування до 100 000, але грант пам'яті не збільшується, і оцінки, що виходять з сортування, все ще базуються на різних значеннях таблиці.

введіть тут опис зображення

Аналогічно налаштування порогу витрат на паралелізм, щоб запит отримував паралельний план, було достатньо у 130 випадку для повернення до нижчої оцінки. Додавання QUERYTRACEON 8757також спричиняє нижчу оцінку. Схоже, 10% -на оцінка зберігається лише для тривіальних планів.

Ваша запропонована перепишіть с

WHERE TheString LIKE 'ZZ[_]%'

Показує значно кращі оцінки обом. Вихід для цього є

  CSelCalcTrieBased

      Column: QCOL: [MyStringTestDB].[dbo].[StringTest].TheString

Показано, що він використовував спроби . Більш детальна інформація про це можна знайти в розділі коротких рядків статистики трохи вище тут .

Однак це не те саме, що ваш оригінальний запит. Як _вважається, перший екземпляр завжди є третім символом, а не динамічно.

Якщо це припущення важко закодовано у ваш оригінальний запит

 WHERE SUBSTRING(TheString, 1, 3) = 'ZZ_'

Метод оцінки змінюється, CSelCalcHistogramComparison(INTERVAL)і оціночні рядки стають точними.

Він здатний перетворити це в діапазон

WHERE TheString >=  'ZZ_' AND TheString < ???

і використовуйте гістограму для оцінки кількості рядків зі значеннями в цьому діапазоні.

Однак це стосується лише оцінки кардинальності. LIKEє кращим, оскільки він може використовувати діапазон пошуку під час виконання. SUBSTRING(TheString, 1, 3)або LEFT(TheString, 3)не може.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.