Я испытываю невероятно высокую оценку количества элементов для следующего запроса:
SELECT dm.PRIMARY_ID
FROM
(
SELECT COALESCE(d1.JOIN_ID, d2.JOIN_ID, d3.JOIN_ID) PRIMARY_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
) dm
INNER JOIN X_LAST_TABLE lst ON dm.PRIMARY_ID = lst.JOIN_ID;
Ориентировочный план здесь . Я работаю над статистической копией таблиц, поэтому не могу включить фактический план. Однако я не думаю, что это очень актуально для этой проблемы.
По оценкам SQL Server, из производной таблицы «dm» будет возвращено 481577 строк. Затем он оценивает, что 4528030000 строк будет возвращено после выполнения соединения с X_LAST_TABLE, но JOIN_ID является первичным ключом X_LAST_TIME. Я ожидаю, что оценка кардинальности соединения будет между 0 и 481577 строками. Вместо этого оценка строки составляет 10% от числа строк, которые я получу при перекрестном соединении внешней и внутренней таблиц. Математика для этого работает с округлением: 481577 * 94025 * 0,1 = 45280277425, округленное до 4528030000.
Я в первую очередь ищу основную причину этого поведения. Меня также интересуют простые обходные пути, но, пожалуйста, не предлагайте менять модель данных или использовать временные таблицы. Этот запрос является упрощением логики в представлении. Я знаю, что делать COALESCE на нескольких столбцах и объединять их не очень хорошая практика. Часть цели этого вопроса - выяснить, нужно ли мне рекомендовать изменить модель данных.
Я тестирую на Microsoft SQL Server 2014 с включенной устаревшей оценкой мощности. TF 4199 и другие включены. Я могу дать полный список флагов трассировки, если это окажется актуальным.
Вот наиболее подходящее определение таблицы:
CREATE TABLE X_LAST_TABLE (
JOIN_ID NUMERIC(18, 0) NOT NULL
CONSTRAINT PK_X_LAST_TABLE PRIMARY KEY CLUSTERED (JOIN_ID ASC)
);
Я также написал все сценарии создания таблиц и их статистику, если кто-то захочет воспроизвести проблему на одном из своих серверов.
Чтобы добавить несколько моих наблюдений, использование TF 2312 фиксирует оценку, но это не вариант для меня. TF 2301 не фиксирует оценку. Удаление одной из таблиц фиксирует оценку. Как ни странно, изменение порядка соединения X_DETAIL_LINK также исправляет оценку. Под изменением порядка соединения я подразумеваю переписывание запроса, а не форсирование порядка соединения с подсказкой. Вот примерный план запроса при изменении порядка соединений.
bigint
вместо того,decimal(18, 0)
чтобы получить преимущества: 1) используйте 8 байтов вместо 9 для каждого значения и 2) используйте сопоставимый с байтами тип данных вместо упакованного типа данных, что может иметь последствия для процессорного времени при сравнении значений.X_DETAIL2
and,X_DETAIL3
если ониJOIN_ID
не равны nullX_DETAIL1
?Ответы:
Генерировать хорошие оценки мощности и распределения достаточно сложно, когда схема имеет формат 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
предикатом. Дерево операторов также имеет последнее внутреннее объединение , при котором порядок эвристического объединения не смог переместить дерево вверх в более предпочтительную позицию. Оставляя в стороне все скаляры и проекции, дерево соединений:Обратите внимание, что ошибочная окончательная оценка уже установлена. Он печатается как
Card=4.52803e+009
и сохраняется внутри как значение с плавающей запятой двойной точности 4.5280277425e + 9 (4528027742.5 в десятичном виде).Производная таблица в исходном запросе была удалена, а проекции нормализованы. SQL-представление дерева, на котором была выполнена начальная оценка мощности и кардинальности:
(Кроме того, повторение
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% предположения):Результатом является селективность 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 строке для всей статистической основы, которую имеет вычисление.Это разумное ожидание, даже если принять, что оценка количества элементов может происходить в разное время (во время оптимизации на основе затрат) для физически разных, но логически и семантически идентичных поддеревьев - при этом окончательный план является своего рода сшитым лучшим из лучший (на одну памятную группу). Отсутствие общеплановой гарантии согласованности не означает, что отдельное объединение должно быть способным нарушить респектабельность, я понимаю.
С другой стороны, если мы в конечном итоге догадываемся о последней инстанции , надежда уже потеряна, так зачем беспокоиться. Мы испробовали все известные нам трюки и сдались. Если не сказать ничего другого, дикая окончательная оценка является отличным предупреждением о том, что во время компиляции и оптимизации этого запроса не все было хорошо внутри CE.
Когда я попробовал MCVE, 120+ CE вывел нулевую (= 1) итоговую оценку строки (как вложенную
ISNULL
) для исходного запроса, что также неприемлемо для моего мышления.Реальное решение, вероятно, включает в себя изменение дизайна, чтобы позволить простые экви-соединения без
COALESCE
илиISNULL
, и в идеале внешние ключи и другие ограничения, полезные для компиляции запроса.источник
Я полагаю, что
Compute Scalar
оператор,COALESCE(d1.JOIN_ID, d2.JOIN_ID, d3.JOIN_ID)
являющийся результатом присоединения,X_LAST_TABLE.JOIN_ID
является основной причиной проблемы. Исторически сложилось так, что вычислить скаляры было трудно точно 1 , 2 .Поскольку вы предоставили минимально полный проверяемый пример (спасибо!) С точной статистикой, я могу переписать запрос так, чтобы объединение больше не требовало расширенных
CASE
функциональных возможностей, чтоCOALESCE
привело к гораздо более точным оценкам строки, очевидно, к большей Точная общая стоимостьСм. приложение в конце. :Хотя
xID IS NOT NULL
технически это не требуется, посколькуID = JOIN_ID
они не будут объединяться с нулевыми значениями, я включил их, поскольку они более четко отображают намерение.План 1 и План 2
План 1:
План 2:
Новый запрос выигрывает (?) От распараллеливания. Также следует отметить, что у нового запроса есть выходное число строк, равное 1, что на самом деле может оказаться хуже в конце дня, чем оценка 4528030000 для исходного запроса. Стоимость поддерева для оператора выбора в новом запросе составляет 243210, а первоначальная - 536,535, что явно меньше. Сказав это, я не верю, что первая оценка приблизилась к реальности.
Приложение 1.
После дальнейших консультаций с разными людьми по поводу The Heap ™, подстегнутых обсуждением с @Lamak, кажется, что мой вышеописанный запрос работает ужасно, даже с параллелизмом. Решение , которое позволяет как производительность хорошей и хорошие оценки мощности состоит в замене
COALESCE(x,y,z)
сISNULL(ISNULL(x, y), z)
, как в:COALESCE
CASE
оптимизатор запросов превращает в оператор "под прикрытием". Таким образом, оценщику кардинальности сложнее найти надежную статистику для столбцов, скрытых внутриCOALESCE
.ISNULL
внутренняя функция гораздо более «открыта» для оценки мощности. Также ничегоISNULL
не стоит оптимизировать, если известно, что цель не обнуляется.План для
ISNULL
варианта выглядит так:(Вставьте версию плана здесь ).
К вашему сведению, подпишитесь на Sentry One за их замечательный Plan Explorer, который я использовал для создания графических планов выше.
источник
В соответствии с вашим условием соединения, таблица может быть организована очень многими способами, это «изменение в один конкретный способ» исправит результат.
Предположим, что объединение только одной таблицы даст вам правильный результат.
Здесь вместо
X_DETAIL_1
, вы можете использовать либоX_DETAIL_2
илиX_DETAIL_3
.Поэтому цель отдыха 2 таблицы не ясна.
Это как у вас сломать стол
X_DETAIL_1
на 2 части.Скорее всего , « есть ошибка , где вы заполнения этих таблиц. » В идеале
X_DETAIL_1
,X_DETAIL_2
иX_DETAIL_3
должны содержать одинаковое количество строк.Но одна или несколько таблиц содержат нежелательное количество строк.
Извините, если я ошибаюсь.
источник