Підказка про кардинальність SQL Server


14

Чи існує спосіб, як "ввести" оцінку кардинальності оптимізатору SQL Server (будь-яку версію)?

тобто щось подібне до натяку на кардинальність Oracle.

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


[1] Leis, Viktor та ін. "Наскільки насправді оптимізатори запитів?"
Праці Фонду 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 рядків, але оцінка рядка з неї змінюється з вхідним параметром. Припустимо, що вам потрібно збільшити оцінку рядків із похідної таблиці на 10X. Один із способів досягти цього:

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;

Ми бачимо, що базова таблиця становить 10X:

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, представлені Адамом Маханіком.


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


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Ключове слово може бути зазначено замість буквального в підказці , щоб використовувати загальний середню потужність Замість оцінки на основі фактичного значення параметра і статистику гистограмме.

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