Чому LEN () функціонує погано заниженою кардинальністю у SQL Server 2014?


26

У мене є таблиця зі стовпчиком рядка та присудком, який перевіряє наявність рядків з певною довжиною. У SQL Server 2014 я бачу оцінку в 1 рядок незалежно від довжини, на яку я перевіряю. Це дає дуже погані плани, оскільки насправді є тисячі чи навіть мільйони рядків, і SQL Server вирішує поставити цю таблицю на зовнішній бік вкладеного циклу.

Чи є пояснення для оцінки кардинальності 1.0003 для SQL Server 2014, тоді як SQL Server 2012 оцінює 31 622 рядки? Чи є хороший спосіб вирішення?

Ось коротке відтворення питання:

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

Для попереднього СЕ я бачу, що оцінка припадає на 3,16228% рядків - і це євристичне "магічне число", яке використовується для стовпців = буквальні предикати (є й інші евристики, засновані на побудові предиката - але LENобернена навколо стовпця для застарілі результати СЕ відповідають цій загадковій рамці). Приклади цього можна побачити у публікації про здогадки про селективність за відсутності статистики Джо Сака та оцінки постійного порівняння Іанна Хосе.

-- 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функції.

Можливе вирішення

Отримати оцінку на основі трійки можна в обох моделях CE, переписавши LENяк LIKE:

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

ПОДОБНИЙ план


Інформація про використані прапорці Trace:

  • 2363: показує багато інформації, включаючи завантаження статистики.
  • 3604: друкує вихід команд DBCC на вкладку повідомлень.

13

Чи є пояснення для оцінки кардинальності 1.0003 для SQL 2014, тоді як SQL 2012 оцінює 31 622 рядки?

Я думаю, що відповідь @ Zane досить добре висвітлює цю частину.

Чи є хороший спосіб вирішення?

Ви можете спробувати створити Неперсинований обчислений стовпець для LEN(cust_nbr)(та (необов'язково) створити Некластеризований індекс у цьому обчисленому стовпчику. Це повинно отримати точну статистику.

Я зробив кілька тестувань, і ось що я знайшов:

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

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