Мне трудно понять, почему SQL Server предложил оценку, которая может быть легко доказана как несовместимая со статистикой.
консистенция
Нет общей гарантии согласованности. Оценки могут быть рассчитаны для разных (но логически эквивалентных) поддеревьев в разное время, используя разные статистические методы.
Нет ничего плохого в логике, которая гласит, что объединение этих двух идентичных поддеревьев должно создавать перекрестный продукт, но в равной степени нечего сказать, что выбор рассуждений более обоснован, чем любой другой.
Начальная оценка
В вашем конкретном случае начальная оценка мощности объединения не выполняется для двух идентичных поддеревьев. . Форма дерева в это время:
LogOp_Join
LogOp_GbAgg
LogOp_LeftOuterJoin
LogOp_Get TBL: ar
LogOp_Select
LogOp_Get TBL: tcr
ScaOp_Comp x_cmpEq
ScaOp_Identifier [tcr] .rId
Значение ScaOp_Const = 508
ScaOp_Logical x_lopAnd
ScaOp_Comp x_cmpEq
ScaOp_Identifier [ar] .fId
ScaOp_Identifier [tcr] .fId
ScaOp_Comp x_cmpEq
ScaOp_Identifier [ar] .bId
ScaOp_Identifier [tcr] .bId
AncOp_PrjList
AncOp_PrjEl Expr1003
ScaOp_AggFunc stopMax
ScaOp_Convert int
ScaOp_Identifier [tcr] .isS
LogOp_Select
LogOp_GbAgg
LogOp_LeftOuterJoin
LogOp_Get TBL: ar
LogOp_Select
LogOp_Get TBL: tcr
ScaOp_Comp x_cmpEq
ScaOp_Identifier [tcr] .rId
Значение ScaOp_Const = 508
ScaOp_Logical x_lopAnd
ScaOp_Comp x_cmpEq
ScaOp_Identifier [ar] .fId
ScaOp_Identifier [tcr] .fId
ScaOp_Comp x_cmpEq
ScaOp_Identifier [ar] .bId
ScaOp_Identifier [tcr] .bId
AncOp_PrjList
AncOp_PrjEl Expr1006
ScaOp_AggFunc stopMin
ScaOp_Convert int
ScaOp_Identifier [ar] .isT
AncOp_PrjEl Expr1007
ScaOp_AggFunc stopMax
ScaOp_Convert int
ScaOp_Identifier [tcr] .isS
ScaOp_Comp x_cmpEq
ScaOp_Identifier Expr1006
Значение ScaOp_Const = 1
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [ar] .fId
ScaOp_Identifier QCOL: [ar] .fId
При первом входе соединения был упрощен непроецированный агрегат, а при втором входе соединения предикат t.isT = 1
помещен под ним, где t.isT
есть MIN(CONVERT(INT, ar.isT))
. Несмотря на это, вычисление селективности для isT
предиката можно использовать CSelCalcColumnInInterval
на гистограмме:
CSelCalcColumnInInterval
Колонка: COL: Expr1006
Загруженная гистограмма для столбца QCOL: [ar] .isT из статистики с идентификатором 3
Селективность: 4.85248e-005
Создана коллекция статистики:
CStCollFilter (ID = 11, CARD = 1)
CStCollGroupBy (ID = 10, CARD = 20608)
CStCollOuterJoin (ID = 9, CARD = 20608 x_jtLeftOuter)
CStCollBaseTable (ID = 3, CARD = 20608 TBL: ar)
CStCollFilter (ID = 8, CARD = 1)
CStCollBaseTable (ID = 4, CARD = 28 TBL: tcr)
(Правильное) ожидание - это 20 608 строк, которые будут уменьшены до 1 строки этим предикатом.
Присоединиться к оценке
Теперь возникает вопрос, как 20 608 строк из другого входного соединения будут совпадать с этой одной строкой:
LogOp_Join
CStCollGroupBy (ID = 7, CARD = 20608)
CStCollOuterJoin (ID = 6, CARD = 20608 x_jtLeftOuter)
...
CStCollFilter (ID = 11, CARD = 1)
CStCollGroupBy (ID = 10, CARD = 20608)
...
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [ar] .fId
ScaOp_Identifier QCOL: [ar] .fId
Существует несколько разных способов оценки объединения в целом. Мы могли бы, например:
- Получите новые гистограммы у каждого оператора плана в каждом поддереве, выровняйте их при объединении (при необходимости интерполируя значения шагов) и посмотрите, как они совпадают; или
- Выполните более простое «грубое» выравнивание гистограмм (используя минимальные и максимальные значения, а не пошагово); или
- Вычислить отдельные селективности только для столбцов объединения (из базовой таблицы и без какой-либо фильтрации), а затем добавить эффект избирательности предикатов, не связанных с объединением.
- ...
В зависимости от используемой оценки кардинальности и некоторых эвристик может быть использован любой из них (или вариация). Дополнительную информацию см. В официальном документе Microsoft « Оптимизация планов запросов с помощью оценщика мощности SQL Server 2014» .
Ошибка?
Теперь, как отмечено в вопросе, в этом случае «простое» одностолбцовое соединение (on fId
) использует CSelCalcExpressionComparedToExpression
калькулятор:
План для расчета:
CSelCalcExpressionComparedToExpression [ar] .fId x_cmpEq [ar] .fId
Загруженная гистограмма для столбца QCOL: [ar] .bId из статистики с идентификатором 2
Загруженная гистограмма для столбца QCOL: [ar] .fId из статистики с идентификатором 1
Селективность: 0
Этот расчет оценивает, что объединение 20 608 строк с 1 отфильтрованной строкой будет иметь нулевую селективность: ни одна строка не будет соответствовать (сообщается как одна строка в окончательных планах). Это неправильно? Да, возможно, здесь есть ошибка в новом CE. Можно утверждать, что 1 строка будет соответствовать всем строкам или ни одной, поэтому результат может быть разумным, но есть основания полагать иначе.
Детали на самом деле довольно хитры, но ожидание, что оценка будет основана на нефильтрованных fId
гистограммах, измененных селективностью фильтра, дающего 20608 * 20608 * 4.85248e-005 = 20608
строки, очень разумно.
Следование этим расчетам будет означать использование калькулятора CSelCalcSimpleJoinWithDistinctCounts
вместо CSelCalcExpressionComparedToExpression
. Нет задокументированного способа сделать это, но если вам интересно, вы можете включить недокументированный флаг трассировки 9479:
Обратите внимание, что при окончательном объединении получается 20 608 строк из двух однорядных входных данных, но это не должно быть сюрпризом. Это тот же план, который был разработан оригинальным CE под TF 9481.
Я упоминал, что детали сложны (и требуют много времени для исследования), но, насколько я могу судить, основная причина проблемы связана с предикатом rId = 508
с нулевой избирательностью. Эта нулевая оценка повышается до одной строки обычным способом, что, по-видимому, способствует оценке нулевой избирательности в рассматриваемом соединении, когда она учитывает более низкие предикаты во входном дереве (отсюда загрузка статистики для bId
).
Если разрешить внешнему объединению сохранять оценку внутренней стороны нулевой строки (вместо повышения до одной строки) (чтобы все внешние строки соответствовали требованиям), то получится оценка объединения без ошибок с любым калькулятором. Если вы заинтересованы в изучении этого, недокументированный флаг трассировки - 9473 (один):
Поведение оценки мощности соединения с CSelCalcExpressionComparedToExpression
помощью также может быть изменено, чтобы не учитывать `` bId` с другим недокументированным флагом вариации (9494). Я упоминаю все это, потому что я знаю, что вы заинтересованы в таких вещах; не потому что они предлагают решение. До тех пор, пока вы не сообщите Microsoft о проблеме, и они не решат ее (или нет), возможно, наилучшим способом продвижения вперед будет выражение запроса. Независимо от того, является ли поведение преднамеренным или нет, им должно быть интересно услышать о регрессии.
Наконец, чтобы привести в порядок еще одну вещь, упомянутую в сценарии воспроизведения: конечная позиция фильтра в плане вопроса - это результат исследования на основе затрат, в результате которого GbAggAfterJoinSel
агрегат и фильтр перемещаются над объединением, поскольку выходные данные объединения имеют такой маленький размер. количество рядов Фильтр изначально был ниже объединения, как вы и ожидали.