Почему функция LEN () сильно недооценивает количество элементов в SQL Server 2014?

26

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

Есть ли объяснение для оценки мощности 1.0003 для SQL Server 2014, а для SQL Server 2012 - 31622 строки? Есть ли хороший обходной путь?

Вот краткое воспроизведение вопроса:

-- Create a table with 1MM rows of dummy data
CREATE TABLE #customers (cust_nbr VARCHAR(10) NOT NULL)
GO

INSERT INTO #customers WITH (TABLOCK) (cust_nbr)
    SELECT TOP 1000000 
        CONVERT(VARCHAR(10),
        ROW_NUMBER() OVER (ORDER BY (SELECT NULL))) AS cust_nbr
    FROM master..spt_values v1
    CROSS JOIN master..spt_values v2
GO

-- Looking for string of a certain length.
-- While both CEs yield fairly poor estimates, the 2012 CE is much
-- more conservative (higher estimate) and therefore much more likely
-- to yield an okay plan rather than a drastically understimated loop join.
-- 2012: 31,622 rows estimated, 900K rows actual
-- 2014: 1 row estimated, 900K rows actual
SELECT COUNT(*)
FROM #customers
WHERE LEN(cust_nbr) = 6
OPTION (QUERYTRACEON 9481) -- Optionally, use 2012 CE
GO

Вот более полный скрипт, показывающий дополнительные тесты

Я также прочитал технический документ по оценщику мощности SQL Server 2014 , но не нашел там ничего, что прояснило бы ситуацию.

Джефф Паттерсон
источник

Ответы:

20

Для устаревшего CE, я вижу, оценка для 3,16228% строк - и это эвристика "магического числа", используемая для предикатов column = literal (существуют другие эвристики, основанные на построении предикатов - но LENобернутые вокруг столбца для унаследованные результаты CE соответствуют этой структуре предположения). Вы можете увидеть примеры этого в посте « Выборочные предположения в отсутствие статистики » Джо Сэка и « Сравнение констант-констант » Иэна Хосе.

-- Legacy CE: 31622.8 rows
SELECT  COUNT(*)
FROM    #customers
WHERE   LEN(cust_nbr) = 6
OPTION  ( QUERYTRACEON 9481); -- Legacy CE
GO

Теперь, что касается нового поведения CE, похоже, что это теперь видно оптимизатору (что означает, что мы можем использовать статистику). Я просмотрел вывод калькулятора ниже, и вы можете посмотреть на ассоциированную автогенерацию статистики в качестве указателя:

-- New CE: 1.00007 rows
SELECT  COUNT(*)
FROM    #customers
WHERE   LEN(cust_nbr) = 6
OPTION  ( QUERYTRACEON 2312 ); -- New CE
GO

-- View New CE behavior with 2363 (for supported option use XEvents)
SELECT  COUNT(*)
FROM    #customers
WHERE   LEN(cust_nbr) = 6
OPTION  (QUERYTRACEON 2312, QUERYTRACEON 2363, QUERYTRACEON 3604, RECOMPILE); -- New CE
GO

/*
Loaded histogram for column QCOL:
[tempdb].[dbo].[#customers].cust_nbr from stats with id 2
Using ambient cardinality 1e+006 to combine distinct counts:
  999927

Combined distinct count: 999927
Selectivity: 1.00007e-006
Stats collection generated:
  CStCollFilter(ID=2, CARD=1.00007)
      CStCollBaseTable(ID=1, CARD=1e+006 TBL: #customers)

End selectivity computation
*/

EXEC tempdb..sp_helpstats '#customers';


--Check out AVG_RANGE_ROWS values (for example - plenty of ~ 1)
DBCC SHOW_STATISTICS('tempdb..#customers', '_WA_Sys_00000001_B0368087');
--That's my Stats name yours is subject to change

К сожалению, логика основывается на оценке количества различных значений, которая не корректируется с учетом эффекта LENфункции.

Возможное решение

Вы можете получить оценку TRIE на основе обеих моделей под CE, переписав LENкак LIKE:

SELECT COUNT_BIG(*)
FROM #customers AS C
WHERE C.cust_nbr LIKE REPLICATE('_', 6);

Как план


Информация об используемых флагах трассировки:

  • 2363: показывает много информации, включая загружаемую статистику.
  • 3604: выводит вывод команд DBCC на вкладку сообщений.
Зейн
источник
13

Есть ли объяснение для оценки мощности 1.0003 для SQL 2014, а для SQL 2012 - 31622 строки?

Я думаю, что ответ @ Zane довольно хорошо освещает эту часть.

Есть ли хороший обходной путь?

Вы можете попробовать создать непостоянный вычисляемый столбец LEN(cust_nbr)и (необязательно) создать некластерный индекс для этого вычисляемого столбца. Это должно дать вам точную статистику.

Я провел некоторое тестирование, и вот что я нашел:

  • Статистические данные автоматически создавались в непостоянном вычисляемом столбце, если для него не был определен индекс.
  • Добавление некластеризованного индекса в вычисляемый столбец не только не помогло, но немного повлияло на производительность. Немного выше процессор и прошедшее время. Немного выше оценочная стоимость (чего бы это ни стоило).
  • Создание вычисляемого столбца как PERSISTED(без индекса) было лучше, чем два других варианта. Расчетные ряды были более точными. Процессор и истекшее время были лучше (как и ожидалось, так как не нужно ничего вычислять для каждой строки).
  • Мне не удалось создать отфильтрованный индекс или отфильтрованную статистику для вычисляемого столбца (из-за того, что он был вычислен), даже если он был PERSISTED:-(
Соломон Руцкий
источник
1
Спасибо за тщательное сравнение между сохранением и нет. Полезно знать, что даже если у сохраняемого вычисляемого столбца есть свои преимущества, непостоянный столбец может быть очень быстрым выигрышем с очень небольшими накладными расходами в некоторых случаях, когда статистика по выражению является полезной.
Джефф Паттерсон