Я знаю, що робити COALESCE
на кількох стовпцях і приєднуватись до них - не надто хороша практика.
Створення хороших оцінок кардинальності та розповсюдження досить складно, коли схема становить 3NF + (з ключами та обмеженнями), а запит є реляційним і в першу чергу SPJG (вибір-проекція-приєднання-група за). Модель СЕ побудована на цих принципах. Чим більше незвичних або нереляційних особливостей у запиті, тим ближче до меж того, з чим можуть працювати рамки кардинальності та вибірковості. Зайдіть занадто далеко, і СЕ здасться та здогадається .
Більшість прикладів 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. Це не випадки застосування простої логіки (із секретною формулою) до кожного оператора. СЕ знає про ключові та функціональні залежності; він знає, як оцінити, використовуючи частоти, багатоваріантну статистику та гістограми; і існує абсолютна кількість спеціальних справ, вдосконалень, стримувань і противаг та допоміжних конструкцій. Він часто оцінює, наприклад, приєднується декількома способами (частота, гістограма) і приймає рішення про результат або коригування, виходячи з відмінностей між ними.
Остання основна річ, яку слід охопити: Початкова оцінка кардинальності виконується для кожної операції в дереві запитів знизу вгору. Вибірковість і кардинальність спочатку виводяться для операторів листів (базові відносини). Модифіковані гістограми та інформація про щільність / частоту отримують для батьківських операторів. Чим далі вгору по дереву ми йдемо, тим нижчою є якість оцінок, оскільки помилки, як правило, накопичуються.
Ця єдина початкова комплексна оцінка дає вихідну точку і виникає задовго до того, як будь-який розгляд буде розглянуто підсумковим планом виконання (це трапляється ще до етапу складання тривіального плану). Дерево запитів у цей момент має тенденцію відображати письмову форму запиту досить близько (хоча з видаленими підзапити та застосованими спрощеннями тощо)
Одразу після початкової оцінки SQL Server здійснює евристичне переупорядкування з'єднання, яке, вільно кажучи, намагається перевпорядкувати дерево для розміщення менших таблиць, і першою приєднується висока селективність. Він також намагається розташувати внутрішні з'єднання перед зовнішніми з'єднаннями та перехресними виробами. Його можливості не великі; його зусилля не є вичерпними; і він не враховує фізичні витрати (оскільки вони ще не існують - є лише статистична інформація та інформація метаданих). Евристичний порядок найбільш вдалий на простих внутрішніх деревах еквіоїну. Він існує, щоб забезпечити «кращу» вихідну точку оптимізації на основі витрат.
Чому така оцінка кардинальності приєднання настільки велика?
MCVE має "незвичайне" в основному надлишкове приєднання багатьох до багатьох , а в присудку приєднується еквівалент COALESCE
. Дерево Оператора також має внутрішнє з'єднання в минулому , що евристичний приєднатися до перезаказа був не в змозі рухатися вгору по дереву в більш детально визначений становище. Не залишаючи осторонь усіх скалярів та проекцій, дерево з'єднання:
LogOp_Join [ Card=4.52803e+009 ]
LogOp_LeftOuterJoin [ Card=481577 ]
LogOp_LeftOuterJoin [ Card=481577 ]
LogOp_LeftOuterJoin [ Card=481577 ]
LogOp_LeftOuterJoin [ Card=481577 ]
LogOp_Get TBL: X_DRIVING_TABLE(alias TBL: dt) [ Card=481577 ]
LogOp_Get TBL: X_DETAIL_1(alias TBL: d1) [ Card=70 ]
LogOp_Get TBL: X_DETAIL_LINK(alias TBL: lnk) [ Card=47 ]
LogOp_Get TBL: X_DETAIL_2(alias TBL: d2) X_DETAIL_2 [ Card=119 ]
LogOp_Get TBL: X_DETAIL_3(alias TBL: d3) X_DETAIL_3 [ Card=281 ]
LogOp_Get TBL: X_LAST_TABLE(alias TBL: lst) X_LAST_TABLE [ Card=94025 ]
Зверніть увагу, несправна остаточна оцінка вже діє. Він друкується як Card=4.52803e+009
і зберігається внутрішньо як значення подвійної точності з плаваючою точкою 4,5280277425e + 9 (4528027742,5 в десятковій частині).
Виведена таблиця в оригінальному запиті видалена, а прогнози нормалізовані. Представлення SQL дерева, на якому було виконано початкову оцінку кардинальності та селективності:
SELECT
PRIMARY_ID = COALESCE(d1.JOIN_ID, d2.JOIN_ID, d3.JOIN_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
INNER JOIN X_LAST_TABLE lst
ON lst.JOIN_ID = COALESCE(d1.JOIN_ID, d2.JOIN_ID, d3.JOIN_ID)
(В бік відхилення повтор COALESCE
також присутній у кінцевому плані - один раз у кінцевому Скалярному Скалярі та один раз на внутрішній стороні внутрішнього з'єднання).
Помітьте остаточне приєднання. Це внутрішнє з'єднання є (за визначенням) декартовим твором X_LAST_TABLE
та попереднім об'єднаним результатом із застосованим виділенням (присудок приєднання) lst.JOIN_ID = COALESCE(d1.JOIN_ID, d2.JOIN_ID, d3.JOIN_ID)
. Кардинальність декартового продукту просто 481577 * 94025 = 45280277425.
Для цього нам потрібно визначити і застосувати вибірковість присудка. Поєднання непрозорого розгорнутого COALESCE
дерева (з точки зору UNION
та IIF
, пам’ятайте) разом із впливом на ключову інформацію, отримані гістограми та частоти попередніх "незвичайних" здебільшого надлишкових комбінованих "багато-багато" комбінованих засобів означає, що СЕ не в змозі отримати прийнятну оцінку будь-яким із звичайних способів.
Як результат, він входить у логіку «Вгадай». Логіка здогадок помірно складна, з випробуваними алгоритмами здогадок "утворених" та "не так освічених". Якщо не знайдено кращої основи для здогадки, модель використовує здогадку в крайньому випадку, яке для порівняння рівності: sqllang!x_Selectivity_Equal
= фіксована 0,1 селективність (10% здогадка):
-- the moment of doom
movsd xmm0,mmword ptr [sqllang!x_Selectivity_Equal
Результат - 0,1 селективність для декартового продукту: 481577 * 94025 * 0,1 = 4528027742,5 (~ 4,552803e + 009), як згадувалося раніше.
Переписує
Коли проблематичне з'єднання прокоментується , робиться краща оцінка, оскільки уникається "здогадки про останню інстанцію" (ключова інформація зберігається 1-М приєднаннями). Якість оцінки все ще низька, оскільки COALESCE
предикат приєднання зовсім не сприятливий для СЕ. Переглянута оцінка робить по крайней мере виглядати більш розумним людям, я вважаю.
Коли запит записується із зовнішнім приєднанням до X_DETAIL_LINK
останнього , евристичний порядок може поміняти його на остаточне внутрішнє з'єднання X_LAST_TABLE
. Поставлення внутрішнього з'єднання безпосередньо поруч із зовнішнім завданням проблеми дає обмеженим можливостям раннього впорядкування можливість покращити остаточну оцінку, оскільки наслідки здебільшого надлишкового "незвичайного" зовнішнього з'єднання багатьох-багатьох наступають після складної оцінки вибірковості для COALESCE
. Знову ж таки, оцінки трохи кращі, ніж тверді здогадки, і, ймовірно, не витримають рішучого перехресного допиту в суді.
Упорядкування суміші внутрішніх і зовнішніх з'єднань є складним та трудомістким (навіть повна оптимізація на етапі 2 лише намагається обмежити підмножину теоретичних кроків).
Вкладений ISNULL
у відповідь Макса Вернона вдається уникнути фіксованої здогадки, але остаточна оцінка - це неправдоподібні нульові рядки (підвищені до одного ряду для пристойності). Це також може бути фіксованою здогадкою в 1 рядок для всіх статистичних підрахунків, які має розрахунок.
Я б очікував, що оцінка прихильності приєднатися між 0 і 481577 рядками.
Це розумне очікування, навіть якщо можна визнати, що оцінка кардинальності може відбуватися в різний час (під час оптимізації, заснованої на витратах) на фізично різних, але логічно та семантично однакових підрядках - остаточний план є свого роду зшитим найкращим кращий (за групою пам’яті). Відсутність загальнопланової гарантії узгодженості не означає, що індивідуальне приєднання повинне бути здатним плавати на респектабельність, я це розумію.
З іншого боку, якщо ми закінчимося здогадками в останню чергу , надія вже втрачена, то навіщо турбуватися. Ми спробували всі відомі хитрощі, і відмовилися. Якщо нічого іншого, дика остаточна оцінка є чудовим попереджувальним знаком того, що не все пройшло всередині СЕ під час складання та оптимізації цього запиту.
Коли я спробував MCVE, 120+ CE створив нульову (= 1) рядкову підсумкову оцінку (як вкладена ISNULL
) для оригінального запиту, що так само неприйнятно для мого мислення.
Справжнє рішення, ймовірно, передбачає зміну дизайну, щоб дозволити просте приєднання рівних без COALESCE
або ISNULL
, в ідеалі зовнішніх ключів та інших обмежень, корисних для складання запитів.
bigint
замість цього,decimal(18, 0)
ви отримаєте переваги: 1) використовуйте 8 байт замість 9 для кожного значення та 2) використовуйте порівнянний з байтом тип даних замість упакованого типу даних, що може мати наслідки за час процесора при порівнянні значень.