Почему не присоединиться к работе по удалению с sys.query_store_plan?

10

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

CREATE TABLE #tears
(
    plan_id bigint NOT NULL
);

INSERT #tears (plan_id) 
VALUES (1);

SELECT
    T.plan_id
FROM #tears AS T
LEFT JOIN sys.query_store_plan AS QSP
    ON QSP.plan_id = T.plan_id;

plan_idКолонна документирована как первичный ключ sys.query_store_plan, но план выполнения не использует присоединиться к ликвидации , как можно было бы ожидать:

  1. Никакие атрибуты не проецируются из DMV.
  2. Первичный ключ DMV plan_idне может дублировать строки из временной таблицы
  3. A LEFT JOINиспользуется, поэтому никакие строки не Tмогут быть удалены.

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

план графика

Почему это так, и что можно сделать, чтобы получить исключение соединений здесь?

Пол Уайт 9
источник

Ответы:

16

Документация немного вводит в заблуждение. DMV является нематериализованным представлением и не имеет первичного ключа как такового. Базовые определения немного сложны, но упрощенное определение sys.query_store_plan:

CREATE VIEW sys.query_store_plan AS
SELECT
    PPM.plan_id
    -- various other attributes
FROM sys.plan_persist_plan_merged AS PPM
LEFT JOIN sys.syspalvalues AS P
    ON P.class = 'PFT' 
    AND P.[value] = plan_forcing_type;

Кроме того, sys.plan_persist_plan_mergedэто также представление, хотя необходимо подключиться через выделенное соединение администратора, чтобы увидеть его определение. Опять упрощенно

CREATE VIEW sys.plan_persist_plan_merged AS   
SELECT     
    P.plan_id as plan_id,    
    -- various other attributes
FROM sys.plan_persist_plan P 
    -- NOTE - in order to prevent potential deadlock
    -- between QDS_STATEMENT_STABILITY LOCK and index locks   
    WITH (NOLOCK) 
LEFT JOIN sys.plan_persist_plan_in_memory PM
    ON P.plan_id = PM.plan_id;

Индексы по sys.plan_persist_plan:

╔════════════════════════╦════════════════════════ ══════════════╦═════════════╗
║ index_name ║ index_description ║ index_keys ║
╠════════════════════════╬════════════════════════ ══════════════╬═════════════╣
║ plan_persist_plan_cidx ║ кластеризованный, уникальный, расположенный на PRIMARY ║ plan_id ║
║ plan_persist_plan_idx1 ║ некластеризованный, расположенный на PRIMARY ║ query_id (-) ║
╚════════════════════════╩════════════════════════ ══════════════╩═════════════╝

Так plan_idчто вынужден быть уникальным sys.plan_persist_plan.

Теперь sys.plan_persist_plan_in_memoryэто потоковая табличная функция, представляющая табличное представление данных, хранящихся только во внутренних структурах памяти. Как таковой, он не имеет никаких уникальных ограничений.

По сути, выполняемый запрос, таким образом, эквивалентен:

DECLARE @t1 table (plan_id integer NOT NULL);
DECLARE @t2 table (plan_id integer NOT NULL UNIQUE CLUSTERED);
DECLARE @t3 table (plan_id integer NULL);

SELECT 
    T1.plan_id
FROM @t1 AS T1 
LEFT JOIN
(
    SELECT 
        T2.plan_id
    FROM @t2 AS T2
    LEFT JOIN @t3 AS T3 
        ON T3.plan_id = T2.plan_id
) AS Q1
    ON Q1.plan_id = T1.plan_id;

... который не производит исключение соединения:

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

Если разобраться в сути проблемы, проблема заключается во внутреннем запросе:

DECLARE @t2 table (plan_id integer NOT NULL UNIQUE CLUSTERED);
DECLARE @t3 table (plan_id integer NULL);

SELECT 
    T2.plan_id
FROM @t2 AS T2
LEFT JOIN @t3 AS T3 
    ON T3.plan_id = T2.plan_id;

... очевидно, что левое соединение может привести @t2к дублированию строк , поскольку @t3не имеет ограничений по уникальности plan_id. Следовательно, соединение не может быть устранено:

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

Чтобы обойти это, мы можем явно сказать оптимизатору, что нам не нужны повторяющиеся plan_idзначения:

DECLARE @t2 table (plan_id integer NOT NULL UNIQUE CLUSTERED);
DECLARE @t3 table (plan_id integer NULL);

SELECT DISTINCT
    T2.plan_id
FROM @t2 AS T2
LEFT JOIN @t3 AS T3 
    ON T3.plan_id = T2.plan_id;

Внешнее соединение с @t3теперь может быть устранено:

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

Применяя это к реальному запросу:

SELECT DISTINCT
    T.plan_id
FROM #tears AS T
LEFT JOIN sys.query_store_plan AS QSP
    ON QSP.plan_id = T.plan_id;

Точно так же мы могли бы добавить GROUP BY T.plan_idвместо DISTINCT. В любом случае, оптимизатор теперь может правильно рассуждать об plan_idатрибуте вплоть до вложенных представлений и по желанию исключать оба внешних объединения:

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

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

Пол Уайт 9
источник