Изменения в оценках для предикатов, которые содержат 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)не могу.

Мартин Смит
источник