Звідки беруться ці постійні сканування та ліві зовнішні з'єднання у тривіальному плані SELECT запитів?


21

У мене є ця таблиця:

CREATE TABLE [dbo].[Accounts] (
    [AccountId] UNIQUEIDENTIFIER UNIQUE NOT NULL DEFAULT NEWID(),
    -- WHATEVER other columns
);
GO
CREATE UNIQUE CLUSTERED INDEX [AccountsIndex]
    ON [dbo].[Accounts]([AccountId] ASC);
GO

Цей запит:

DECLARE @result UNIQUEIDENTIFIER
SELECT @result = AccountId FROM Accounts WHERE AccountId='guid-here'

виконується з планом запитів, що складається з одного пошукового індексу - як очікувалося:

SELECT <---- Clustered Index Seek

Цей запит робить те саме:

DECLARE @result UNIQUEIDENTIFIER
SET @result = (SELECT AccountId FROM Accounts WHERE AccountId='guid-here')

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

SELECT <--- Compute Scalar <--- Left Outer Join <--- Constant Scan
                                      ^
                                      |------Clustered Index Seek

Що то за магія? Що робить це постійне сканування з наступним лівим зовнішнім приєднанням?

Відповіді:


29

Семантика двох тверджень різна:

  • Перший не встановлює значення змінної, якщо не знайдено рядка.
  • Другий завжди встановлює змінну, включаючи нульову, якщо не знайдено рядка.

Постійне сканування створює порожній рядок (без стовпців!), Що призведе до оновлення змінної у випадку, якщо з базової таблиці нічого не збігається. Лівий з’єднання гарантує, що порожній рядок пережив приєднання. Змінне призначення можна вважати таким, що відбувається в кореневому вузлі плану виконання.

Використання SELECT @result

-- Set initial value
DECLARE @result uniqueidentifier = {guid 'FE2CA909-1162-4C6C-A7AC-33B257E28539'};

-- @result does not change
SELECT @result = AccountId 
FROM Accounts 
WHERE AccountId={guid '7AD4D33C-1ED7-4183-B7F3-48C33D666525'};

SELECT @result;

Результат 1

Використання SET @result

-- Set initial value
DECLARE @result uniqueidentifier = {guid 'FE2CA909-1162-4C6C-A7AC-33B257E28539'};

-- @result set to null
SET @result = 
(
    SELECT AccountId 
    FROM Accounts 
    WHERE AccountId={guid '7AD4D33C-1ED7-4183-B7F3-48C33D666525'}
);

SELECT @result;

Результат 2

Плани виконання

ВИБІР присвоєнняЖоден рядок не надходить до кореневого вузла, тому присвоєння не відбувається.

Призначення SETРядок завжди надходить у кореневий вузол, тому відбувається присвоєння змінної.


Про додаткове постійне сканування та вкладені петлі зліва назовні приєднання нічого не турбує. Зокрема, з'єднання дешеве, оскільки на його зовнішньому вході гарантовано зустріти один рядок, і максимум один рядок (у вашому прикладі) на внутрішньому вході.

Існують й інші способи забезпечення рядку, створеного з підзапиту, щоб забезпечити призначення змінної. Перший полягає у використанні надмірного скалярного сукупності (немає групи за умовами):

-- Set initial value
DECLARE @result uniqueidentifier = {guid 'FE2CA909-1162-4C6C-A7AC-33B257E28539'};

-- @result set to null
SET @result = 
    (
        SELECT MAX(AccountId)
        FROM Accounts 
        WHERE AccountId={guid '7AD4D33C-1ED7-4183-B7F3-48C33D666525'} 
    );
SELECT @result;

Результат 3

Скалярний загальний план виконання

Зауважте, скалярний сукупність створює рядок, навіть якщо він не отримує введення.

Документація:

Якщо оператор SELECT не повертає жодних рядків, змінна зберігає теперішнє значення. Якщо вираз - скалярний підзапит, який не повертає значення, змінна встановлюється в NULL.

Для призначення змінних рекомендуємо використовувати SET @local_variable замість SELECT @local_variable.

Подальше читання:

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