Как SQL Server узнает, что предикаты взаимосвязаны?

15

При диагностировании запросов SQL Server 2008 R2 с плохой оценкой количества элементов (несмотря на простое индексирование, обновленную статистику и т. Д.) И, следовательно, с плохими планами запросов, я обнаружил, возможно, связанную статью базы знаний: FIX: низкая производительность при выполнении запроса который содержит коррелированные предикаты AND в SQL Server 2008 или SQL Server 2008 R2 или в SQL Server 2012

Я могу догадаться, что статья KB подразумевает под «коррелированным», например, предикат № 2 и предикат № 1 в основном нацелены на одни и те же строки.

Но я не знаю, как SQL Server знает об этих корреляциях. Нужен ли таблице многостолбцовый индекс, содержащий столбцы из обоих предикатов? Использует ли SQL статистику, чтобы проверить, связаны ли значения из одного столбца с другим? Или какой-то другой метод используется?

Я спрашиваю об этом по двум причинам:

  1. определить, какие из моих таблиц и запросов могут быть улучшены с помощью этого исправления
  2. знать, что я должен делать в индексировании, статистике и т. д., чтобы повлиять на # 1
Джастин Грант
источник

Ответы:

20

Рассмотрим простой запрос и план выполнения AdventureWorks, показанные ниже. Запрос содержит предикаты, связанные с AND. Оценка кардинальности оптимизатора составляет 41 211 строк:

-- Estimate 41,211 rows
SELECT COUNT_BIG(*)
FROM Production.TransactionHistory AS TH
WHERE 
    TH.TransactionID BETWEEN 100000 AND 168336
    AND TH.TransactionDate BETWEEN '2007-09-01' AND '2008-03-13';

План выполнения по умолчанию

Использование статистики по умолчанию

Учитывая только статистику по одному столбцу, оптимизатор производит эту оценку, оценивая количество элементов для каждого предиката в отдельности и умножая полученную селективность вместе. Эта эвристика предполагает, что предикаты полностью независимы.

Разделение запроса на две части упрощает вычисление:

-- Estimate 68,336.4 rows
SELECT COUNT_BIG(*)
FROM Production.TransactionHistory AS TH
WHERE 
    TH.TransactionID BETWEEN 100000 AND 168336;

Таблица истории транзакций содержит в общей сложности 113 443 строки, поэтому оценка 68 336,4 представляет селективность 68336,4 / 113443 = 0,60238533 для этого предиката. Эта оценка получается с использованием информации гистограммы для TransactionIDстолбца и значений констант, указанных в запросе.

-- Estimate 68,413 rows
SELECT COUNT_BIG(*)
FROM Production.TransactionHistory AS TH
WHERE 
    TH.TransactionDate BETWEEN '2007-09-01' AND '2008-03-13';

Этот предикат имеет оценочную селективность 68413,0 / 113443 = 0,60306056 . Опять же, он рассчитывается на основе постоянных значений предиката и гистограммы TransactionDateобъекта статистики.

Предполагая, что предикаты полностью независимы, мы можем оценить селективность двух предикатов вместе, умножив их вместе. Окончательная оценка количества элементов получается умножением результирующей селективности на 113 443 строки в базовой таблице:

0,6038533 * 0,60306056 * 113443 = 41210,987

После округления это 41 211 оценка, показанная в исходном запросе (оптимизатор также использует внутреннюю математику с плавающей запятой).

Не очень хорошая оценка

TransactionIDИ TransactionDateстолбцы имеют тесную корреляцию данных AdventureWorks набор (как монотонно возрастающие ключи и столбцы дат часто делают). Эта корреляция означает, что предположение о независимости нарушается. Как следствие, план запроса после выполнения показывает 68 095 строк, а не 41 411:

План после выполнения

Флаг трассировки 4137

Включение этого флага трассировки изменяет эвристику, используемую для объединения предикатов. Вместо полной независимости оптимизатор считает, что селективности двух предикатов достаточно близки, чтобы их можно было коррелировать:

-- Estimate 68,336.4
SELECT COUNT_BIG(*)
FROM Production.TransactionHistory AS TH
WHERE 
    TH.TransactionID BETWEEN 100000 AND 168336
    AND TH.TransactionDate BETWEEN '2007-09-01' AND '2008-03-13'
OPTION (QUERYTRACEON 4137);

Напомним, что TransactionIDодин только предикат оценил 68,336,4 строки, а TransactionDateодин только предикат оценил 68,413 строк. Оптимизатор выбрал нижнюю из этих двух оценок, а не умножающую селективность.

Конечно, это просто другая эвристика, но она может помочь улучшить оценки для запросов с коррелированными ANDпредикатами. Каждый предикат рассматривается для возможной корреляции, и есть другие корректировки, сделанные, когда ANDзадействовано много предложений, но этот пример служит для демонстрации его основ.

Статистика по нескольким столбцам

Это может помочь в запросах с корреляциями, но информация гистограммы по-прежнему основана исключительно на ведущем столбце статистики. Таким образом, следующие статистические данные-кандидаты отличаются по важности:

CREATE STATISTICS
    [stats Production.TransactionHistory TransactionID TransactionDate]
ON Production.TransactionHistory
    (TransactionID, TransactionDate);

CREATE STATISTICS
    [stats Production.TransactionHistory TransactionDate TransactionID]
ON Production.TransactionHistory
    (TransactionDate, TransactionID);

Взяв только один из них, мы видим, что единственная дополнительная информация - это дополнительные уровни плотности «все». Гистограмма все еще содержит только подробную информацию о TransactionDateстолбце.

DBCC SHOW_STATISTICS
    (
        'Production.TransactionHistory', 
        'stats Production.TransactionHistory TransactionDate TransactionID'
    );

Многостолбцовая статистика

С этой многостолбцовой статистикой на месте ...

SELECT COUNT_BIG(*)
FROM Production.TransactionHistory AS TH
WHERE 
    TH.TransactionID BETWEEN 100000 AND 168336
    AND TH.TransactionDate BETWEEN '2007-09-01' AND '2008-03-13';

... план выполнения показывает оценку, точно такую ​​же, как тогда, когда была доступна только статистика по одному столбцу:

Многостолбцовый план статистики

Пол Уайт восстановил Монику
источник