Постійне спулінг сканування


14

У мене є таблиця з кількома десятками рядів. Спрощена установка наступна

CREATE TABLE #data ([Id] int, [Status] int);

INSERT INTO #data
VALUES (100, 1), (101, 2), (102, 3), (103, 2);

І у мене є запит, який приєднує цю таблицю до набору побудованих рядків зі значенням таблиці (з змінних та констант), наприклад

DECLARE @id1 int = 101, @id2 int = 105;

SELECT
    COALESCE(p.[Code], 'X') AS [Code],
    COALESCE(d.[Status], 0) AS [Status]
FROM (VALUES
        (@id1, 'A'),
        (@id2, 'B')
    ) p([Id], [Code])
    FULL JOIN #data d ON d.[Id] = p.[Id];

План виконання запитів показує, що рішення оптимізатора полягає у використанні FULL LOOP JOINстратегії, яка видається доцільною, оскільки обидва входи мають дуже мало рядків. Одне, що я помітив (і не можу погодитись), - це те, що рядки TVC збираються в маску (див. Область плану виконання в червоному полі).

Постійне спулінг сканування

Чому оптимізатор вводить сюди золотник, в чому причина цього робити? Поза котушкою немає нічого складного. Схоже, це не потрібно. Як позбутися від цього в цьому випадку, які можливі способи?


Наведений вище план був отриманий на

Microsoft SQL Server 2014 (SP2-CU11) (KB4077063) - 12.0.5579.0 (X64)


Пов'язані пропозиції на сайті feedback.azure.com
i-one

Відповіді:


19

Чому оптимізатор вводить сюди золотник, в чому причина цього робити? Поза котушкою немає нічого складного.

Справа поза котушкою не є простою посиланням на таблицю, яку можна просто дублювати, коли генерується ліва альтернатива приєднання / антиполовина .

Це може виглядати трохи як таблиця (постійне сканування), але оптимізатору * це UNION ALLокремі рядки в VALUESпункті.

Додаткової складності вистачає для того, щоб оптимізатор міг вибрати котушку та повторно виконувати рядки, а не замінювати котушку згодом простою таблицею. Наприклад, початкове перетворення з повного об'єднання виглядає так:

ранній план

Зауважте додаткові котушки, введені загальним перетворенням. Шпулі над простою таблицею отримують пізніше за допомогою правила SpoolGetToGet.

Якби оптимізатор мав відповідне SpoolConstGetToConstGetправило, він, в принципі, може працювати як ви хочете.

Як позбутися від цього в цьому випадку, які можливі способи?

Використовуйте реальну таблицю (тимчасову або змінну) або запишіть перетворення з повного з'єднання вручну, наприклад:

WITH 
    p([Id], [Code]) AS
    (
        SELECT @id1, 'A'
        UNION ALL
        SELECT @id2, 'B'
    ),
    FullJoin AS
    (
        SELECT
            p.Code,
            d.[Status]
        FROM p
        LEFT JOIN #data d 
            ON d.[Id] = p.[Id]
        UNION ALL
        SELECT
            NULL,
            D.[Status]
        FROM #data AS D
        WHERE NOT EXISTS
        (
            SELECT *
            FROM p
            WHERE p.Id = D.Id
        )
    )
SELECT
    COALESCE(FullJoin.Code, 'X') AS Code,
    COALESCE(FullJoin.Status, 0) AS [Status]
FROM FullJoin;

План переписання вручну:

План переписання вручну

Орієнтовна вартість становить 0,0067201 одиниць, порівняно з 0,0203412 одиниць для оригіналу.


* Можна спостерігати , як LogOp_UnionAllв Старовинне дерево (TF 8605). У дереві вводу (TF 8606) це a LogOp_ConstTableGet. У перетвореному Дереві показує дерево експресії оптимізатора елементів після синтаксичного аналізу, нормалізації, algebrization, зв'язування і деяких інших підготовчих робіт. Input Дерево показує елементи після перетворення до заперечення нормальній формі (NNF новонаверненого), постійна часу виконання руйнується, і кілька інших бітів і гойдається. Перетворення NNF включає в себе логіку для згортання логічних об'єднань, а серед іншого отримує загальна таблиця.


3

Шпулька таблиці просто створює таблицю з двох наборів кортежів, присутніх у VALUESпункті.

Ви можете усунути котушку, вставивши спочатку ці значення у таблицю темп, наприклад:

DROP TABLE IF EXISTS #data;
CREATE TABLE #data ([Id] int, [Status] int);

INSERT INTO #data
VALUES (100, 1), (101, 2), (102, 3), (103, 2);

DROP TABLE IF EXISTS #p;
CREATE TABLE #p
(
    Id int NOT NULL
    , Code char(1) NOT NULL
);

DECLARE @id1 int = 101, @id2 int = 105;

INSERT INTO #p (Id, Code)
VALUES
        (@id1, 'A'),
        (@id2, 'B');


SELECT
    COALESCE(p.[Code], 'X') AS [Code],
    COALESCE(d.[Status], 0) AS [Status]
FROM #p p
    FULL JOIN #data d ON d.[Id] = p.[Id];

Дивлячись на план виконання вашого запиту, ми бачимо, що вихідний список містить два стовпці, в яких використовується Unionпрефікс; це натяк на те, що золотник створює таблицю з джерела об'єднання:

введіть тут опис зображення

FULL OUTER JOINВимагає SQL Server для доступу до значень в pдва рази, один раз для кожної «сторони» з'єднання. Створення котушки дозволяє отриманим внутрішнім петельним з'єднанням отримати доступ до спільних даних.

Цікаво, що якщо ви заміните на FULL OUTER JOINa LEFT JOINі a RIGHT JOIN, а UNIONрезультати разом, SQL Server не використовує котушку.

SELECT
    COALESCE(p.[Code], 'X') AS [Code],
    COALESCE(d.[Status], 0) AS [Status]
FROM (VALUES
        (101, 'A'),
        (105, 'B')
    ) p([Id], [Code])
    LEFT JOIN #data d ON d.[Id] = p.[Id]
UNION
SELECT
    COALESCE(p.[Code], 'X') AS [Code],
    COALESCE(d.[Status], 0) AS [Status]
FROM (VALUES
        (101, 'A'),
        (105, 'B')
    ) p([Id], [Code])
    RIGHT JOIN #data d ON d.[Id] = p.[Id];

введіть тут опис зображення

Зауважте, я не пропоную використовувати UNIONзапит вище; для великих наборів введення він може бути не більш ефективним, ніж простий, який FULL OUTER JOINви вже маєте.


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