Почему изменение объявленного порядка столбцов соединения приводит к сортировке?

40

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

Тестовая настройка

Сценарий установки, включая некоторые реалистичные статистические данные:

DROP TABLE IF EXISTS #left;
DROP TABLE IF EXISTS #right;

CREATE TABLE #left (
    a       char(4) NOT NULL,
    b       char(2) NOT NULL,
    c       varchar(13) NOT NULL,
    d       bit NOT NULL,
    e       char(4) NOT NULL,
    f       char(25) NULL,
    g       char(25) NOT NULL,
    h       char(25) NULL
    --- and a few other columns
);

CREATE UNIQUE CLUSTERED INDEX IX ON #left (a, b, c, d, e, f, g, h)

UPDATE STATISTICS #left WITH ROWCOUNT=63800000, PAGECOUNT=186000;

CREATE TABLE #right (
    a       char(4) NOT NULL,
    b       char(2) NOT NULL,
    c       varchar(13) NOT NULL,
    d       bit NOT NULL,
    e       char(4) NOT NULL,
    f       char(25) NULL,
    g       char(25) NOT NULL,
    h       char(25) NULL
    --- and a few other columns
);

CREATE CLUSTERED INDEX IX ON #right (a, b, c, d, e, f, g, h)

UPDATE STATISTICS #right WITH ROWCOUNT=55700000, PAGECOUNT=128000;

Репро

Когда я объединяю эти две таблицы по их ключам кластеризации, я ожидаю соединения MERGE один-ко-многим, например:

SELECT *
FROM #left AS l
LEFT JOIN #right AS r ON
    l.a=r.a AND
    l.b=r.b AND
    l.c=r.c AND
    l.d=r.d AND
    l.e=r.e AND
    l.f=r.f AND
    l.g=r.g AND
    l.h=r.h
WHERE l.a='2018';

Это план запроса, который я хочу:

Это то, что я хочу.

(Не берите в голову предупреждения, они имеют отношение к поддельной статистике.)

Однако, если я изменю порядок столбцов в соединении, вот так:

SELECT *
FROM #left AS l
LEFT JOIN #right AS r ON
    l.c=r.c AND     -- used to be third
    l.a=r.a AND     -- used to be first
    l.b=r.b AND     -- used to be second
    l.d=r.d AND
    l.e=r.e AND
    l.f=r.f AND
    l.g=r.g AND
    l.h=r.h
WHERE l.a='2018';

... Это случилось:

План запроса после изменения объявленного порядка столбцов в соединении.

Кажется, оператор Sort упорядочивает потоки в соответствии с заявленным порядком объединения, т. c, a, b, d, e, f, g, hЕ. Добавляет операцию блокировки в мой план запроса.

Вещи, на которые я смотрел

  • Я попытался изменить столбцы на те NOT NULLже результаты.
  • Исходная таблица была создана с помощью ANSI_PADDING OFF, но создание ее с помощью ANSI_PADDING ONне влияет на этот план.
  • Я попробовал INNER JOINвместо LEFT JOIN, без изменений.
  • Я обнаружил это в 2014 году с пакетом обновления 2 (SP2 Enterprise), создал репродукцию для разработчика в 2017 году (текущий CU).
  • Удаление предложения WHERE в ведущем столбце индекса создает хороший план, но это как бы влияет на результаты .. :)

Наконец, мы подошли к вопросу

  • Это намеренно?
  • Могу ли я исключить сортировку без изменения запроса (который является кодом поставщика, поэтому я бы предпочел не делать этого ...). Я могу изменить таблицу и индексы.
Даниэль Хутмахер
источник

Ответы:

28

Это намеренно?

Это по замыслу, да. К сожалению, лучший общедоступный источник для этого утверждения был потерян, когда Microsoft удалила сайт обратной связи Connect, уничтожив множество полезных комментариев от разработчиков из группы SQL Server.

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

Тем не менее, оптимизатор достаточно хорош (во многих случаях), чтобы избежать ненужной сортировки, но этот результат обычно происходит по причинам, отличным от агрессивного использования различных комбинаций порядка. В этом смысле речь идет не столько о «пространстве поиска», сколько о сложном взаимодействии между функциями ортогонального оптимизатора, которые, как было показано, повышают качество общего плана при приемлемой стоимости.

Например, сортировки часто можно избежать, просто сопоставив требование к упорядочению (например, верхнего уровня ORDER BY) с существующим индексом. В вашем случае это тривиально, это может означать добавление, ORDER BY l.a, l.b, l.c, l.d, l.e, l.f, l.g, l.h;но это чрезмерное упрощение (и недопустимое, потому что вы не хотите изменять запрос).

В более общем смысле каждая группа памятки может быть связана с требуемыми или желаемыми свойствами, которые могут включать в себя порядок ввода. Когда нет очевидной причины для принудительного исполнения определенного порядка (например, для удовлетворения ORDER BYили для обеспечения правильных результатов от чувствительного к порядку физического оператора), возникает элемент «удачи». Я написал больше об особенностях этого, поскольку это касается объединения слиянием (в режиме объединения или объединения) в Избегании сортировок с объединением слиянием слиянием . Многое из этого выходит за рамки поддерживаемой поверхности продукта, поэтому рассматривайте его как информационный и подверженный изменениям.

В вашем конкретном случае, да, вы можете настроить индексирование, как предлагает jadarnel27, чтобы избежать сортировки; хотя есть небольшая причина, чтобы фактически предпочесть объединение слияния здесь. Вы также можете указать нам выбор между физическим объединением хэшей или циклом, OPTION(HASH JOIN, LOOP JOIN)используя Руководство по планированию без изменения запроса, в зависимости от ваших знаний о данных и компромисса между лучшей, худшей и средней производительностью.

Наконец, в качестве любопытства отметим, что сортировки можно избежать с помощью простого ORDER BY l.b, за счет потенциально менее эффективного объединения слиянием «многие ко многим» в bодиночку со сложным остатком. Я упоминаю это главным образом как иллюстрацию взаимодействия между функциями оптимизатора, о которых я упоминал ранее, и способ распространения требований верхнего уровня.

Пол Уайт говорит, что GoFundMonica
источник
19

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

Если вы можете изменить индексы, то изменение порядка индексов #rightв соответствии с порядком фильтров в объединении удалит сортировку (для меня):

CREATE CLUSTERED INDEX IX ON #right (c, a, b, d, e, f, g, h)

Удивительно (по крайней мере для меня), это не приводит ни к одному запросу, заканчивающемуся сортировкой.

Это намеренно?

Глядя на вывод некоторых странных флагов трассировки , есть интересная разница в окончательной структуре Memo:

скриншот окончательной структуры памятки для каждого запроса

Как вы можете видеть в «корневой группе» вверху, оба запроса имеют возможность использовать объединение слиянием в качестве основной физической операции для выполнения этого запроса.

Хороший запрос

Объединение без сортировки управляется опцией 1 группы 29 и опцией 1 группы 31 (каждая из которых является сканированием диапазона по задействованным индексам). Он фильтруется по группе 27 (не показана), которая представляет собой серию операций логического сравнения, которые фильтруют объединение.

Неверный запрос

Один с сортировкой определяется (новыми) вариантами 3, которые есть у каждой из этих двух групп (29 и 31). Вариант 3 выполняет физическую сортировку результатов сканирования диапазона, упомянутого ранее (вариант 1 каждой из этих групп).

Зачем?

По какой-то причине возможность использовать 29.1 и 31.1 напрямую в качестве источников для объединения слиянием даже не доступна оптимизатору во втором запросе. В противном случае, я думаю, что он будет включен в корневую группу среди других вариантов. Если бы он был доступен вообще, то он определенно выбрал бы те, которые намного дороже операций сортировки.

Я могу только заключить, что либо:

  • это ошибка (или, скорее, ограничение) в алгоритме поиска оптимизатора
    • изменение индексов и объединений, чтобы иметь только 5 ключей, удаляет сортировку для второго запроса (все ключи 6, 7 и 8 имеют сортировку).
    • Это означает, что область поиска с 8 ключами настолько велика, что оптимизатор просто не успевает определить решение без сортировки в качестве жизнеспособного варианта, прежде чем оно завершается рано по причине «достаточно хороший план найден».
    • мне действительно кажется немного глючным, что порядок условий соединения так сильно влияет на процесс поиска оптимизатора, но на самом деле это немного над моей головой
  • сортировка необходима для обеспечения правильности результатов
    • этот вариант кажется маловероятным, поскольку запрос может выполняться без сортировки, когда ключей меньше или ключи заданы в другом порядке.

Надеюсь, кто-нибудь придет и объяснит, почему такая сортировка необходима, но я подумал, что разница в здании Memo была достаточно интересной, чтобы опубликовать ответ.

Джош Дарнелл
источник
1
Я считаю, что ваш комментарий о поисковом пространстве на самом деле имеет место здесь. чтобы использовать только индексы, оптимизатор должен убедиться, что они достаточны для условий, за 5 клавишами слишком много возможностей проверить, прежде чем он будет вынужден выполнить откат. Мне было бы любопытно, если бы перечислялись все комбинации ордеров в запросе, сколько бы оптимизатор преуспел в сравнении с
откатом
И да, несоответствие действительно немного ошибочно, но, вероятно, оно полностью зависит от алгоритма, используемого для проверки достаточности индексов. Если бы все комбинации были протестированы, вы, вероятно, сможете увидеть шаблон в результатах и ​​определить, какой алгоритм используется. Могу поспорить, что написано, чтобы работать оптимально для более типичных случаев использования. Может существовать альтернатива, которая смогла бы надежно найти 8-клавишное решение в течение срока, но это медленнее, чем текущее решение, когда имеется менее, скажем, 3-4 ключа.
Мистер Миндор