Подсказка мощности SQL Server

14

Есть ли способ, как «внедрить» оценку мощности в оптимизатор SQL Server (любая версия)?

то есть что-то похожее на подсказку мощности Oracle.

Моей мотивацией является статья « Насколько хороши оптимизаторы запросов?» [1] , где они проверяют влияние оценки мощности на выбор плохого плана. Следовательно, было бы достаточно, если бы я мог заставить SQL Server «оценить» мощности точно для сложных запросов.


[1] Лейс, Виктор и др. "Насколько хороши оптимизаторы запросов?"
Материалы фонда VLDB 9.3 (2015): 204-215.

Радим Бача
источник

Ответы:

10

Вы можете получить нечто похожее на CARDINALITYподсказку Oracle, стратегически используя TOPи определяемую пользователем функцию, MANY() разработанную Адамом Мачаником . Давайте рассмотрим несколько примеров. Я использую свободно доступную базу данных AdventureWorks. Предположим, что мне действительно нужно контролировать количество строк, возвращаемых thпроизводной таблицей в следующем запросе:

SELECT 
    p.Name
    , th.ProductId
    , th.Quantity
    , th.ActualCost
FROM Production.Product p
INNER JOIN (
    SELECT ProductId, Quantity, ActualCost
    FROM Production.TransactionHistory 
) th ON p.ProductID = th.ProductID;

Как есть, я получаю оценку 113443 строк:

начальный запрос

Если мне нужно снизить оценку от, thя могу использовать TOPвместе с OPTIMIZE FORподсказкой запроса, чтобы установить цель строки. Вот один из способов сделать это:

DECLARE @row_goal BIGINT = 9223372036854775807;
SELECT 
    p.Name
    , th.ProductId
    , th.Quantity
    , th.ActualCost
FROM Production.Product p
INNER JOIN (
    SELECT TOP (@row_goal) ProductId, Quantity, ActualCost
    FROM Production.TransactionHistory 
) th ON p.ProductID = th.ProductID
OPTION (OPTIMIZE FOR (@row_goal = 1));

Мы видим, что оценка составляет всего 1 строку:

1 строка оценки

Я установил @row_goalмаксимально возможное BIGINTзначение, чтобы избежать изменения результатов. OPTIMIZE FORПодсказка запроса инструктирует оптимизатор Оптимизатор запроса как если@row_goal равно 1. Я получу те же результаты , но запрос будет оптимизирован по- разному.

Увеличение оценки мощности более сложно. Мы не можем просто увеличить значение, TOPпотому что оптимизатор поймет, что он не вернет достаточно строк. Однако мы можем использовать MANY()функцию для добавления строк в оценку. Обратите внимание, что MANY()функция всегда будет возвращать 0 строк, но оценка строки из нее изменяется вместе с входным параметром. Предположим, вам нужно увеличить оценку строки из производной таблицы в 10 раз. Один из способов сделать это:

SELECT 
    p.Name
    , th.ProductId
    , th.Quantity
    , th.ActualCost
FROM Production.Product p
INNER JOIN (
    SELECT TOP (9223372036854775807) ProductId, Quantity, ActualCost
    FROM Production.TransactionHistory 
    LEFT OUTER JOIN dbo.Many(10) AS m ON 1=1
) th ON p.ProductID = th.ProductID;

Мы видим, что оценка в 10 раз превышает базовую таблицу:

10X запрос

Было TOPдобавлено лишнее, чтобы оптимизатор не перемещал таблицы. Без этогоMANY() функция может быть применена в неправильном месте в плане.

Можно комбинировать эти два метода, если вы хотите точно переоценить, а не просто умножить количество строк на коэффициент. Например, предположим, что вам действительно нужно, чтобы оценка производной таблицы была ровно 1000000 строк. Один из способов сделать это:

DECLARE @row_goal BIGINT = 9223372036854775807;

SELECT 
    p.Name
    , th.ProductId
    , th.Quantity
    , th.ActualCost
FROM Production.Product p
INNER JOIN (
    SELECT TOP (@row_goal) ProductId, Quantity, ActualCost
    FROM Production.TransactionHistory 
    LEFT OUTER JOIN dbo.Many(10) AS m ON
        1=1
) th ON p.ProductID = th.ProductID
OPTION (OPTIMIZE FOR (@row_goal = 1000000));

Мы видим, что оценка составляет 1000000 строк:

1 М рядов

Я должен предупредить вас, что это передовые методы, которые часто не нужны для оптимизации запросов. Если вы хотите узнать больше, я рекомендую посмотреть Clash of the Row Goals, представленные Адамом Мачаником.


dbo.Many функция

-- By Adam Machanic, reproduced with permission
IF EXISTS (SELECT * FROM sys.objects WHERE name = 'Many' AND OBJECT_SCHEMA_NAME(object_id) = 'dbo')
    DROP FUNCTION dbo.Many
GO
CREATE FUNCTION dbo.Many(@n INT)
RETURNS TABLE AS
RETURN
(
    WITH
    a(x) AS
    (
        SELECT
            *
        FROM
        (
            VALUES
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1),
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1)
        ) AS x0(x)
    )
    SELECT TOP(@n)
        1 AS x
    FROM
        a AS a1,
        a AS a2
    WHERE
        a1.x % 2 = 0
)
GO
Джо Оббиш
источник
9

Невозможно ввести оценку мощности непосредственно в оптимизатор, но в зависимости от того, чего вы хотите достичь, есть несколько вариантов.

Вы можете использовать OPTION (FAST N)подсказку запроса, чтобы ввести цели строк, и, возможно, переписать свой запрос, используя CTE или подзапросы для внедренияTOP...ORDER BY цели строк на основе в различных частях вашего плана выполнения, но я не уверен, насколько эффективным будет ваш результирующий запрос при запуске. играть с более сложными конструкциями.

Для более подробного объяснения см. Внутри оптимизатора: глубина цели .

Если вы хотите повлиять на операторов, выбранных оптимизатором, вам не нужно пытаться вводить оценки количества элементов, но вы можете использовать такие вещи, как OPTION (MERGE JOIN)или, OPTION (HASH JOIN)например, для принудительного использования операторов физического объединения.

Эта статья более подробно описывает влияние на план с помощью подсказок: Управление планами выполнения с помощью подсказок

Если вы хотите исправить план, вы также можете использовать руководство плана.

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


Соответствующее предложение Microsoft Connect: Разрешить указывать подсказку селективности фильтра в запросах по xor88. Microsoft ответила:

Спасибо за ответ. Я вижу потенциальную выгоду от этого. В общем, мы стараемся сделать наше автоматическое поведение как можно лучше и избегать необходимости такого рода подсказок, но, конечно, у нас есть много других подсказок. Мы рассмотрим это в будущем выпуске, но это будет за пределами версии Denali (11.0).

С наилучшими пожеланиями,
Эрик Хансон
Менеджер программы
SQL Server Query Processing

Том V - попробуйте topanswers.xyz
источник
3

Вы можете использовать OPTIMIZE FORподсказку запроса SQL Server для принудительной оценки количества элементов на основе подсказанных значений вместо использования фактического значения (параметров) или неизвестного значения (переменных) во время компиляции. См. Раздел Советы по запросу в документации по SQL Server для получения полной информации.

Например, в приведенном ниже запросе будет рассчитано количество строк на основе гистограммы статистики из подсказанных значений вместо общей средней мощности, как это было бы в случае с локальными переменными.

DECLARE 
      @StartDate datetime = '20150101'
    , @EndDate datetime = '20150102';
SELECT *
FROM dbo.Example
WHERE
    DateColumn BETWEEN  @StartDate AND @EndDate
OPTION(OPTIMIZE FOR(@StartDate = '20100101', @EndDate='20100101'));

Точно так же подсказка может использоваться для параметров, так что оценки основаны на гистограмме статистики из подсказанных значений вместо фактических значений параметров во время компиляции.

DECLARE 
      @StartDate datetime = '20150101'
    , @EndDate datetime = '20150102';
EXECUTE sp_executesql N'SELECT *
        FROM dbo.Example
        WHERE
            DateColumn BETWEEN  @StartDate AND @EndDate
        OPTION(OPTIMIZE FOR(@StartDate = ''20100101'', @EndDate=''20100101''));'
    , N'@StartDate datetime, @EndDate datetime'
    , @StartDate = @StartDate
    , @EndDate = @EndDate;

UNKNOWNКлючевое слово может быть указано вместо буквального в подсказке , чтобы использовать общий среднюю мощность Вместо оценки на основе фактического значения параметра и статистику гистограмме.

Дэн Гусман
источник