Почему поиск не выбран оптимизатором
TL: DR Расширенное определение вычисляемого столбца влияет на способность оптимизатора изначально изменять порядок соединений. С другой отправной точкой оптимизация на основе затрат проходит другой путь через оптимизатор и заканчивается другим выбором окончательного плана.
подробности
Для всех, кроме самых простых запросов, оптимизатор не пытается исследовать что-то вроде всего возможного плана. Вместо этого он выбирает разумно выглядящую отправную точку , а затем затрачивает заложенные в бюджет усилия на изучение логических и физических вариаций на одном или нескольких этапах поиска, пока не найдет разумный план.
Основная причина, по которой вы получаете разные планы (с разными окончательными оценками затрат) для этих двух случаев, заключается в том, что существуют разные отправные точки. Начиная с другого места, оптимизация заканчивается в другом месте (после ограниченного числа итераций исследования и реализации). Я надеюсь, что это достаточно интуитивно понятно.
Отправная точка я уже упоминался, частично основана на текстовом представлении запроса, но сделаны изменения внутреннего представления дерева , как она проходит через разбор, связывание, нормализации и упрощение этапы компиляции запроса.
Важно отметить, что точная начальная точка сильно зависит от начального порядка соединения, выбранного оптимизатором. Этот выбор делается до загрузки статистики и до получения каких-либо оценок количества элементов. Однако общее количество элементов (количество строк) в каждой таблице известно из метаданных системы.
Поэтому первоначальный порядок объединения основан на эвристике . Например, оптимизатор пытается переписать дерево таким образом, чтобы меньшие таблицы объединялись раньше больших, а внутренние объединения выполнялись раньше внешних (и перекрестных).
Наличие вычисляемого столбца мешает этому процессу, в частности, способности оптимизатора проталкивать внешние соединения вниз по дереву запросов. Это связано с тем, что вычисляемый столбец раскрывается в свое базовое выражение до того, как произойдет переупорядочение объединения, и перемещение объединения за сложным выражением намного сложнее, чем перемещение по простой ссылке на столбец.
Используемые деревья довольно большие, но для иллюстрации начальное дерево запросов для невычисляемых столбцов начинается с: (обратите внимание на два внешних соединения в верхней части)
LogOp_Select
LogOp_Apply (x_jtLeftOuter)
LogOp_LeftOuterJoin
LogOp_NAryJoin
LogOp_LeftAntiSemiJoin
LogOp_NAryJoin
LogOp_Get TBL: dbo.table1 (псевдоним TBL: a4)
LogOp_Select
LogOp_Get TBL: dbo.table6 (псевдоним TBL: a3)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [a3] .col18
ScaOp_Const TI (varchar collate 53256, Var, Trim, ML = 16)
LogOp_Select
LogOp_Get TBL: dbo.table1 (псевдоним TBL: a1)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [a1] .col2
ScaOp_Const TI (varchar collate 53256, Var, Trim, ML = 16)
LogOp_Select
LogOp_Get TBL: dbo.table5 (псевдоним TBL: a2)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [a2] .col2
ScaOp_Const TI (varchar collate 53256, Var, Trim, ML = 16)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [a4] .col2
ScaOp_Identifier QCOL: [a3] .col19
LogOp_Select
LogOp_Get TBL: dbo.table7 (псевдоним TBL: a7)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [a7] .col22
ScaOp_Const TI (varchar collate 53256, Var, Trim, ML = 16)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [a4] .col2
ScaOp_Identifier QCOL: [a7] .col23
LogOp_Select
LogOp_Get TBL: таблица1 (псевдоним TBL: cdc)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [cdc] .col6
ScaOp_Const TI (smallint, ML = 2) XVAR (smallint, не принадлежит, Value = 4)
LogOp_Get TBL: dbo.table5 (псевдоним TBL: a5)
LogOp_Get TBL: таблица2 (псевдоним TBL: cdt)
ScaOp_Logical x_lopAnd
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [a5] .col2
ScaOp_Identifier QCOL: [cdc] .col2
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [a4] .col2
ScaOp_Identifier QCOL: [cdc] .col2
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [cdt] .col1
ScaOp_Identifier QCOL: [cdc] .col1
LogOp_Get TBL: таблица3 (псевдоним TBL: ahcr)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [ahcr] .col9
ScaOp_Identifier QCOL: [cdt] .col1
Тот же фрагмент запроса вычисляемого столбца : (обратите внимание, что внешнее соединение гораздо ниже, расширенное определение вычисляемого столбца и некоторые другие тонкие различия в (внутреннем) порядке объединения)
LogOp_Select
LogOp_Apply (x_jtLeftOuter)
LogOp_NAryJoin
LogOp_LeftAntiSemiJoin
LogOp_NAryJoin
LogOp_Get TBL: dbo.table1 (псевдоним TBL: a4)
LogOp_Select
LogOp_Get TBL: dbo.table6 (псевдоним TBL: a3)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [a3] .col18
ScaOp_Const TI (varchar collate 53256, Var, Trim, ML = 16)
LogOp_Select
LogOp_Get TBL: dbo.table1 (псевдоним TBL: a1
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [a1] .col2
ScaOp_Const TI (varchar collate 53256, Var, Trim, ML = 16)
LogOp_Select
LogOp_Get TBL: dbo.table5 (псевдоним TBL: a2)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [a2] .col2
ScaOp_Const TI (varchar collate 53256, Var, Trim, ML = 16)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [a4] .col2
ScaOp_Identifier QCOL: [a3] .col19
LogOp_Select
LogOp_Get TBL: dbo.table7 (псевдоним TBL: a7)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [a7] .col22
ScaOp_Const TI (varchar collate 53256, Var, Trim, ML = 16)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [a4] .col2
ScaOp_Identifier QCOL: [a7] .col23
LogOp_Project
LogOp_LeftOuterJoin
LogOp_Join
LogOp_Select
LogOp_Get TBL: таблица1 (псевдоним TBL: cdc)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [cdc] .col6
ScaOp_Const TI (smallint, ML = 2) XVAR (smallint, не принадлежит, Value = 4)
LogOp_Get TBL: таблица2 (псевдоним TBL: cdt)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [cdc] .col1
ScaOp_Identifier QCOL: [cdt] .col1
LogOp_Get TBL: таблица3 (псевдоним TBL: ahcr)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [ahcr] .col9
ScaOp_Identifier QCOL: [cdt] .col1
AncOp_PrjList
AncOp_PrjEl QCOL: [cdc] .col7
ScaOp_Convert char collate 53256, Null, Trim, ML = 6
ScaOp_IIF varchar collate 53256, Null, Var, Trim, ML = 6
ScaOp_Comp x_cmpEq
ScaOp_Intrinsic isnumeric
ScaOp_Intrinsic right
ScaOp_Identifier QCOL: [cdc] .col4
ScaOp_Const TI (int, ML = 4) XVAR (int, не принадлежит, Value = 4)
ScaOp_Const TI (int, ML = 4) XVAR (int, не принадлежит, значение = 0)
ScaOp_Const TI (varchar collate 53256, Var, Trim, ML = 1) XVAR (varchar, Owned, Value = Len, Data = (0,))
ScaOp_Intrinsic подстрока
ScaOp_Const TI (int, ML = 4) XVAR (int, не принадлежит, Value = 6)
ScaOp_Const TI (int, ML = 4) XVAR (int, не принадлежит, Value = 1)
ScaOp_Identifier QCOL: [cdc] .col4
LogOp_Get TBL: dbo.table5 (псевдоним TBL: a5)
ScaOp_Logical x_lopAnd
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [a5] .col2
ScaOp_Identifier QCOL: [cdc] .col2
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [a4] .col2
ScaOp_Identifier QCOL: [cdc] .col2
Статистика загружается, и начальная оценка мощности выполняется в дереве сразу после установки начального порядка соединения. Наличие объединений в разных заказах также влияет на эти оценки, что также оказывает влияние на последующую оптимизацию на основе затрат.
Наконец, для этого раздела, если внешнее соединение застряло в середине дерева, это может предотвратить дальнейшее сопоставление правил переупорядочения соединений во время оптимизации на основе затрат.
Использование руководства по плану (или, что то же самое, примерUSE PLAN
подсказки для вашего запроса ) меняет стратегию поиска на более целеустремленный подход, руководствуясь общей формой и функциями поставляемого шаблона. Это объясняет, почему оптимизатор может найти один и тот же table1
план поиска как для вычисляемых, так и для не вычисляемых схем столбцов, когда используется руководство плана или подсказка.
Можем ли мы сделать что-то по-другому, чтобы добиться успеха
Это то, о чем вам следует беспокоиться, только если оптимизатор сам не найдет план с приемлемыми характеристиками производительности.
Все обычные инструменты настройки потенциально применимы. Вы можете, например, разбить запрос на более простые части, просмотреть и улучшить доступную индексацию, обновить или создать новую статистику ... и так далее.
Все это может повлиять на оценки количества элементов, пути кода, проходящего через оптимизатор, и незаметным образом повлиять на решения, основанные на затратах.
В конечном итоге вы можете прибегнуть к использованию подсказок (или руководства по планированию), но обычно это не идеальное решение.
Дополнительные вопросы из комментариев
Я согласен, что лучше всего упростить запрос и т. Д., Но есть ли способ (флаг трассировки), чтобы оптимизатор продолжил оптимизацию и достиг того же результата?
Нет, для исчерпывающего поиска нет флага трассировки, и он вам не нужен. Возможное пространство поиска огромно, и времена компиляции, которые превышают возраст вселенной, не будут приняты хорошо. Кроме того, оптимизатор не знает всех возможных логических преобразований (никто не знает).
Кроме того, зачем нужно сложное расширение, поскольку столбец сохраняется? Почему оптимизатор не может избежать его расширения, рассматривать его как обычный столбец и достигать той же начальной точки?
Вычисляемые столбцы расширяются (как и представления), чтобы обеспечить дополнительные возможности оптимизации. Расширение может быть сопоставлено, например, с постоянным столбцом или индексом позже в процессе, но это происходит после того, как начальный порядок соединения фиксирован.