SQL Server 2014: будь-яке пояснення непослідовної оцінки самостільності кардинальності?


27

Розглянемо наступний план запитів у SQL Server 2014:

введіть тут опис зображення

У плані запитів самостійне приєднання ar.fId = ar.fIdдає оцінку в 1 рядок. Однак це логічно суперечлива оцінка: arмає 20,608рядки і лише одне чітке значення fId(точно відображено в статистиці). Тому це з'єднання виробляє повний поперечний добуток рядків ( ~424MMрядків), внаслідок чого запит працює протягом декількох годин.

Мені важко зрозуміти, чому SQL Server придумав оцінку, яку можна так легко довести, що вона не відповідає статистиці. Будь-які ідеї?

Початкове розслідування та додаткові деталі

Виходячи з відповіді Павла тут , схоже, що і евристика SQL 2012, і SQL 2014 для оцінки кардинальності приєднання повинна легко впоратися з ситуацією, коли потрібно порівняти дві однакові гістограми.

Я почав з виведення прапора сліду 2363, але не зміг зрозуміти це легко. Чи означає наступний фрагмент, що SQL Server порівнює гістограми для fIdта bIdдля того, щоб оцінити вибірковість об'єднання, яке використовується лише fId? Якщо так, це, очевидно, не було б правильно. Або я неправильно читаю вихід прапора трасування?

Plan for computation:
  CSelCalcExpressionComparedToExpression( QCOL: [ar].fId x_cmpEq QCOL: [ar].fId )
Loaded histogram for column QCOL: [ar].bId from stats with id 3
Loaded histogram for column QCOL: [ar].fId from stats with id 1
Selectivity: 0

Зауважте, що я придумав кілька обхідних завдань, які містяться в повному сценарії репро-гінгу і доводять цей запит до мілісекунд. Це питання зосереджено на розумінні поведінки, як уникнути її у майбутніх запитах та визначити, чи це помилка, яку слід подати до Microsoft.

Ось повний скрипт repro , ось повний вихід із прапора трасування 2363 , і ось визначення запитів та таблиць у випадку, якщо ви хочете швидко переглянути їх, не відкриваючи повний скрипт:

WITH cte AS (
    SELECT ar.fId, 
        ar.bId,
        MIN(CONVERT(INT, ar.isT)) AS isT,
        MAX(CONVERT(INT, tcr.isS)) AS isS
    FROM  #SQL2014MinMaxAggregateCardinalityBug_ar ar 
    LEFT OUTER JOIN #SQL2014MinMaxAggregateCardinalityBug_tcr tcr
        ON tcr.rId = 508
        AND tcr.fId = ar.fId
        AND tcr.bId = ar.bId
    GROUP BY ar.fId, ar.bId
)
SELECT s.fId, s.bId, s.isS, t.isS
FROM cte s 
JOIN cte t 
    ON t.fId = s.fId 
    AND t.isT = 1

CREATE TABLE #SQL2014MinMaxAggregateCardinalityBug_ar (
    fId INT NOT NULL,
    bId INT NOT NULL,
    isT BIT NOT NULL
    PRIMARY KEY (fId, bId)
)

CREATE TABLE #SQL2014MinMaxAggregateCardinalityBug_tcr (
    rId INT NOT NULL,
    fId INT NOT NULL,
    bId INT NOT NULL,
    isS BIT NOT NULL
    PRIMARY KEY (rId, fId, bId, isS)
)

Відповіді:


23

Мені важко зрозуміти, чому 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:

9479 план

Зауважте, що підсумкове з'єднання дає 20 608 рядків з двох однорядних входів, але це не повинно бути несподіванкою. Це той самий план, який вироблявся оригінальним СЕ під TF 9481.

Я згадав, що деталі складні (і вимагають багато часу для дослідження), але, наскільки я можу сказати, першопричина проблеми пов'язана з предикатом rId = 508, з нульовою вибірковістю. Ця нульова оцінка піднімається до одного рядка звичайним способом, що, здається, сприяє оцінці нульової селективності при розгляді приєднання, коли вона враховує нижчі предикати в дереві вводу (отже, статистику завантаження для bId).

Дозволяючи зовнішньому з'єднанню зберігати нульовий рядок внутрішньої сторони (замість того, щоб збільшувати його до одного рядка) (так що всі зовнішні рядки відповідають умовам), дає оцінку приєднання без помилок з будь-яким калькулятором. Якщо ви зацікавлені в дослідженні цього, недокументований прапор сліду - 9473 (тільки):

9473 план

Поведінка оцінки кардинальності приєднання за CSelCalcExpressionComparedToExpressionдопомогою також може бути змінено таким чином, щоб не враховувати `` bId '' з іншим недокументованим варіантом змін (9494). Я згадую про все це, бо знаю, що ти зацікавився такими речами; не тому, що пропонують рішення. Поки ви не повідомляєте про проблему в Microsoft, і вони не вирішать її (чи ні), висловлення запиту по-різному - це, мабуть, найкращий шлях. Незалежно від того, поведінка навмисна чи ні, їм слід зацікавити почути про регресію.

Нарешті, виправити ще одне, що згадується у сценарії відтворення: остаточне положення фільтра в плані запитань є результатом дослідження на основі витрат, що GbAggAfterJoinSelпереміщує сукупність та фільтр над з'єднанням, оскільки вихід об'єднання має такий невеликий кількість рядків. Спочатку фільтр був нижче з'єднання, як ви і очікували.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.