Я знаю, что делать COALESCE
несколько столбцов и объединять их - не очень хорошая практика.
Генерировать хорошие оценки мощности и распределения достаточно сложно, когда схема имеет формат 3NF + (с ключами и ограничениями), а запрос реляционный и в основном SPJG (selection-projection-join-group by). Модель CE построена на этих принципах. Чем больше необычных или нереляционных функций в запросе, тем ближе он подходит к границам возможностей кардинальности и селективности. Зайди слишком далеко, и CE сдастся и угадай .
Большая часть примера MCVE - это простое SPJ (без G), хотя и с преимущественно внешними эквивалентами (смоделированными как внутреннее соединение плюс анти-полусоединение), а не с более простым внутренним эквивалентным соединением (или полусоединением). Все отношения имеют ключи, хотя никаких внешних ключей или других ограничений. Все соединения, кроме одного, относятся ко многим, и это хорошо.
Исключением является внешнее соединение « многие ко многим» между X_DETAIL_1
и X_DETAIL_LINK
. Единственная функция этого объединения в MCVE - потенциально дублировать строки в X_DETAIL_1
. Это необычная вещь.
Простые предикаты равенства (выборки) и скалярные операторы также лучше. Например, атрибут сравнения-равный атрибут / константа обычно хорошо работает в модели. Относительно «легко» модифицировать гистограммы и статистику частоты, чтобы отразить применение таких предикатов.
COALESCE
основывается на том CASE
, что в свою очередь реализовано как IIF
(и это было верно задолго до того, как IIF
появилось в языке Transact-SQL). СЕ моделирует IIF
как UNION
с двумя взаимоисключающими детьми, каждый из которых состоит из проекта по выбору входного отношения. Каждый из перечисленных компонентов имеет поддержку модели, поэтому объединить их относительно просто. Тем не менее, чем больше однослойных абстракций, тем менее точным будет конечный результат - причина того, почему большие планы выполнения, как правило, менее стабильны и надежны.
ISNULL
с другой стороны, это присуще двигателю. Он не построен с использованием каких-либо более основных компонентов. ISNULL
Например, применить эффект к гистограмме так же просто, как заменить шаг для NULL
значений (и при необходимости сжать). Это все еще относительно непрозрачно, поскольку скалярные операторы идут, и поэтому лучше избегать, где это возможно. Тем не менее, как правило, он более дружественный по отношению к оптимизатору (менее дружественный по отношению к оптимизатору), чем CASE
альтернативный вариант на основе.
CE (70 и 120+) очень сложен, даже по стандартам SQL Server. Это не тот случай применения простой логики (с секретной формулой) к каждому оператору. CE знает о ключах и функциональных зависимостях; он умеет оценивать, используя частоты, многомерную статистику и гистограммы; и есть абсолютная тонна особых случаев, уточнений, сдержек и противовесов и вспомогательных структур. Он часто оценивает, например, соединения несколькими способами (частота, гистограмма) и принимает решение о результате или корректировке на основе различий между ними.
И еще одна важная вещь: начальная оценка мощности выполняется для каждой операции в дереве запросов снизу вверх. Избирательность и мощность выводятся в первую очередь для конечных операторов (базовые отношения). Модифицированные гистограммы и информация о плотности / частоте выводятся для родительских операторов. Чем дальше мы идем вверх по дереву, тем ниже качество оценок, как правило, по мере накопления ошибок.
Эта единственная первоначальная комплексная оценка обеспечивает отправную точку и происходит задолго до того, как какое-либо внимание будет уделено окончательному плану выполнения (это происходит задолго до стадии составления тривиального плана). На этом этапе дерево запросов имеет тенденцию достаточно точно отражать письменную форму запроса (хотя с удаленными подзапросами, примененными упрощениями и т. Д.)
Сразу после первоначальной оценки SQL Server выполняет эвристическое переупорядочение соединений, которое, собственно говоря, пытается переупорядочить дерево для размещения меньших таблиц и соединений с высокой селективностью. Он также пытается позиционировать внутренние соединения перед тем, как внешние соединения и перекрестные продукты. Его возможности не обширны; его усилия не являются исчерпывающими; и он не учитывает физические затраты (поскольку они еще не существуют - присутствуют только статистическая информация и информация метаданных). Эвристическое переупорядочение наиболее успешно на простых деревьях внутреннего равновесия. Он существует, чтобы обеспечить «лучшую» отправную точку для оптимизации на основе затрат.
Почему эта оценка кардинальности соединения настолько велика?
MCVE имеет «необычное» в основном избыточное соединение « многие ко многим» и равное соединение с COALESCE
предикатом. Дерево операторов также имеет последнее внутреннее объединение , при котором порядок эвристического объединения не смог переместить дерево вверх в более предпочтительную позицию. Оставляя в стороне все скаляры и проекции, дерево соединений:
LogOp_Join [ Card=4.52803e+009 ]
LogOp_LeftOuterJoin [ Card=481577 ]
LogOp_LeftOuterJoin [ Card=481577 ]
LogOp_LeftOuterJoin [ Card=481577 ]
LogOp_LeftOuterJoin [ Card=481577 ]
LogOp_Get TBL: X_DRIVING_TABLE(alias TBL: dt) [ Card=481577 ]
LogOp_Get TBL: X_DETAIL_1(alias TBL: d1) [ Card=70 ]
LogOp_Get TBL: X_DETAIL_LINK(alias TBL: lnk) [ Card=47 ]
LogOp_Get TBL: X_DETAIL_2(alias TBL: d2) X_DETAIL_2 [ Card=119 ]
LogOp_Get TBL: X_DETAIL_3(alias TBL: d3) X_DETAIL_3 [ Card=281 ]
LogOp_Get TBL: X_LAST_TABLE(alias TBL: lst) X_LAST_TABLE [ Card=94025 ]
Обратите внимание, что ошибочная окончательная оценка уже установлена. Он печатается как Card=4.52803e+009
и сохраняется внутри как значение с плавающей запятой двойной точности 4.5280277425e + 9 (4528027742.5 в десятичном виде).
Производная таблица в исходном запросе была удалена, а проекции нормализованы. SQL-представление дерева, на котором была выполнена начальная оценка мощности и кардинальности:
SELECT
PRIMARY_ID = COALESCE(d1.JOIN_ID, d2.JOIN_ID, d3.JOIN_ID)
FROM X_DRIVING_TABLE dt
LEFT OUTER JOIN X_DETAIL_1 d1
ON dt.ID = d1.ID
LEFT OUTER JOIN X_DETAIL_LINK lnk
ON d1.LINK_ID = lnk.LINK_ID
LEFT OUTER JOIN X_DETAIL_2 d2
ON dt.ID = d2.ID
LEFT OUTER JOIN X_DETAIL_3 d3
ON dt.ID = d3.ID
INNER JOIN X_LAST_TABLE lst
ON lst.JOIN_ID = COALESCE(d1.JOIN_ID, d2.JOIN_ID, d3.JOIN_ID)
(Кроме того, повторение COALESCE
также присутствует в окончательном плане - один раз в последнем вычислительном скаляре и один раз на внутренней стороне внутреннего соединения).
Обратите внимание на финальное соединение. Это внутреннее соединение является (по определению) декартовым произведением X_LAST_TABLE
и предыдущим выходным соединением с примененным выбором (предикатом соединения) lst.JOIN_ID = COALESCE(d1.JOIN_ID, d2.JOIN_ID, d3.JOIN_ID)
. Кардинальность декартового произведения - это просто 481577 * 94025 = 45280277425.
Для этого нам нужно определить и применить селективность предиката. Комбинация непрозрачного расширенного COALESCE
дерева (с точки зрения UNION
и IIF
, помните) вместе с воздействием на ключевую информацию, производными гистограммами и частотами ранее «необычного» комбинированного внешнего объединения «многие ко многим» означает, что CE не способен получить приемлемую оценку любым из нормальных способов.
В результате он входит в Guess Logic. Логика догадок умеренно сложна, со слоями «образованных» догадок и «не очень образованных» алгоритмов угадывания. Если лучшего основания для предположения не найдено, модель использует предположение последней инстанции, которое для сравнения на равенство: sqllang!x_Selectivity_Equal
= фиксированная селективность 0,1 (10% предположения):
-- the moment of doom
movsd xmm0,mmword ptr [sqllang!x_Selectivity_Equal
Результатом является селективность 0,1 по декартовому продукту: 481577 * 94025 * 0,1 = 4528027742,5 (~ 4,52803e + 009), как указано выше.
Перезапись
Когда проблемное объединение закомментировано , получается лучшая оценка, поскольку избегается «предположение о последней инстанции» с фиксированной избирательностью (ключевая информация сохраняется объединениями 1-M). Качество оценки по-прежнему не совсем достоверно, поскольку COALESCE
предикат объединения вовсе не соответствует CE. Я полагаю, что пересмотренная оценка, по крайней мере, выглядит более разумной для людей.
Когда запрос записывается с внешним соединением, X_DETAIL_LINK
помещенным последним , эвристическое переупорядочение может поменять его с последним внутренним соединением X_LAST_TABLE
. Помещение внутреннего объединения прямо рядом с проблемой « Внешнее объединение» дает ограниченным возможностям раннего переупорядочения возможность улучшить окончательную оценку, поскольку эффекты в основном избыточного «необычного» внешнего объединения «многие ко многим» наступают после сложной оценки избирательности. для COALESCE
. Опять же, оценки немного лучше, чем фиксированные догадки, и, вероятно, не выдержит решительного перекрестного допроса в суде.
Переупорядочение смеси внутренних и внешних объединений является трудным и трудоемким процессом (даже полная стадия оптимизации 2-го этапа допускает только ограниченное подмножество теоретических шагов).
Вложенный ISNULL
предложенный в ответе Макса Вернона удается избежать фиксированного предположения о спасении, но окончательной оценкой является невероятный ноль строк (для приличия поднятый на одну строку). Это также может быть фиксированное предположение о 1 строке для всей статистической основы, которую имеет вычисление.
Я ожидаю, что оценка кардинальности соединения будет между 0 и 481577 строками.
Это разумное ожидание, даже если принять, что оценка количества элементов может происходить в разное время (во время оптимизации на основе затрат) для физически разных, но логически и семантически идентичных поддеревьев - при этом окончательный план является своего рода сшитым лучшим из лучший (на одну памятную группу). Отсутствие общеплановой гарантии согласованности не означает, что отдельное объединение должно быть способным нарушить респектабельность, я понимаю.
С другой стороны, если мы в конечном итоге догадываемся о последней инстанции , надежда уже потеряна, так зачем беспокоиться. Мы испробовали все известные нам трюки и сдались. Если не сказать ничего другого, дикая окончательная оценка является отличным предупреждением о том, что во время компиляции и оптимизации этого запроса не все было хорошо внутри CE.
Когда я попробовал MCVE, 120+ CE вывел нулевую (= 1) итоговую оценку строки (как вложенную ISNULL
) для исходного запроса, что также неприемлемо для моего мышления.
Реальное решение, вероятно, включает в себя изменение дизайна, чтобы позволить простые экви-соединения без COALESCE
или ISNULL
, и в идеале внешние ключи и другие ограничения, полезные для компиляции запроса.
bigint
вместо того,decimal(18, 0)
чтобы получить преимущества: 1) используйте 8 байтов вместо 9 для каждого значения и 2) используйте сопоставимый с байтами тип данных вместо упакованного типа данных, что может иметь последствия для процессорного времени при сравнении значений.