Мені важко зрозуміти, чому SQL Server придумав оцінку, яку можна так легко довести, що вона не відповідає статистиці.
Послідовність
Загальної гарантії послідовності немає. Оцінки можуть бути розраховані на різних (але логічно еквівалентних) підрядках у різний час, використовуючи різні статистичні методи.
Немає нічого поганого в логіці, яка говорить про те, що приєднання цих двох однакових підрядків повинно створити перехресний продукт, але однаково нічого не можна сказати, що вибір міркувань є більш надійним, ніж будь-який інший.
Початкова оцінка
У вашому конкретному випадку початкова оцінка кардинальності приєднання не виконується на двох однакових підрядках . Форма дерева на той час така:
LogOp_Join
LogOp_GbAgg
LogOp_LeftOuterJoin
LogOp_Get TBL: арк
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: арк
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 зі статистики з id 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 White Paper Оптимізація планів запитів з SQL Server 2014 по потужності оцінювач більше.
Помилка?
Тепер, як зазначено в запитанні, у цьому випадку калькулятор fId
використовує "просте" одноколонне з'єднання (увімкнено ) CSelCalcExpressionComparedToExpression
:
План обчислення:
CSelCalcExpressionComparedToExpression [ar] .fId x_cmpEq [ar] .fId
Завантажена гістограма для стовпця QCOL: [ar] .bId зі статистики з id 2
Завантажена гістограма для стовпця QCOL: [ar] .fId зі статистики з id 1
Селективність: 0
Цей розрахунок оцінює, що об'єднання 20 608 рядків з 1 відфільтрованим рядком матиме нульову вибірковість: жодні рядки не збігаються (повідомляється як один рядок у остаточних планах). Це неправильно? Так, напевно, тут є помилка в новому СЕ. Можна стверджувати, що 1 ряд буде відповідати всім рядкам або жодному, тому результат може бути розумним, але є підстави вважати інше.
Деталі насправді досить складні, але сподівання, що оцінка базується на нефільтрованих fId
гістограмах, модифікованих селективністю фільтра, що дає 20608 * 20608 * 4.85248e-005 = 20608
рядки, є дуже розумним.
Виконання цього розрахунку означатиме використання калькулятора, CSelCalcSimpleJoinWithDistinctCounts
а не CSelCalcExpressionComparedToExpression
. Немає документально підтвердженого способу зробити це, але якщо вам цікаво, ви можете ввімкнути недокументований прапор сліду 9479:
Зауважте, що підсумкове з'єднання дає 20 608 рядків з двох однорядних входів, але це не повинно бути несподіванкою. Це той самий план, який вироблявся оригінальним СЕ під TF 9481.
Я згадав, що деталі складні (і вимагають багато часу для дослідження), але, наскільки я можу сказати, першопричина проблеми пов'язана з предикатом rId = 508
, з нульовою вибірковістю. Ця нульова оцінка піднімається до одного рядка звичайним способом, що, здається, сприяє оцінці нульової селективності при розгляді приєднання, коли вона враховує нижчі предикати в дереві вводу (отже, статистику завантаження для bId
).
Дозволяючи зовнішньому з'єднанню зберігати нульовий рядок внутрішньої сторони (замість того, щоб збільшувати його до одного рядка) (так що всі зовнішні рядки відповідають умовам), дає оцінку приєднання без помилок з будь-яким калькулятором. Якщо ви зацікавлені в дослідженні цього, недокументований прапор сліду - 9473 (тільки):
Поведінка оцінки кардинальності приєднання за CSelCalcExpressionComparedToExpression
допомогою також може бути змінено таким чином, щоб не враховувати `` bId '' з іншим недокументованим варіантом змін (9494). Я згадую про все це, бо знаю, що ти зацікавився такими речами; не тому, що пропонують рішення. Поки ви не повідомляєте про проблему в Microsoft, і вони не вирішать її (чи ні), висловлення запиту по-різному - це, мабуть, найкращий шлях. Незалежно від того, поведінка навмисна чи ні, їм слід зацікавити почути про регресію.
Нарешті, виправити ще одне, що згадується у сценарії відтворення: остаточне положення фільтра в плані запитань є результатом дослідження на основі витрат, що GbAggAfterJoinSel
переміщує сукупність та фільтр над з'єднанням, оскільки вихід об'єднання має такий невеликий кількість рядків. Спочатку фільтр був нижче з'єднання, як ви і очікували.