Зберігається обчислена колонка, що викликає сканування


9

Перетворення звичайного стовпця в постійний обчислений стовпець призводить до того, що цей запит не може здійснювати пошуки індексу. Чому?

Тестовано на кількох версіях SQL Server, включаючи 2016 SP1 CU1.

Репрос

Біда в тому , з table1, col7.

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

Як показав Пол Уайт (спасибі!), Пошук доступний, якщо вимушений, тож питання: чому пошук не вибирається оптимізатором, і чи можемо ми зробити щось інакше, щоб пошук стався як слід, не змінюючи код?

Щоб уточнити проблематичну частину, ось відповідне сканування в плані поганого виконання:

план

Відповіді:


12

Чому пошук не вибирається оптимізатором


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: table1 (псевдонім TBL: cdc)
                    ScaOp_Comp x_cmpEq
                        ScaOp_Identifier QCOL: [cdc] .col6
                        ScaOp_Const TI (smallint, ML = 2) XVAR (smallint, не належить, значення = 4)
                LogOp_Get TBL: dbo.table5 (псевдонім TBL: a5) 
                LogOp_Get TBL: table2 (псевдонім 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: table3 (псевдонім 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: table1 (псевдонім TBL: cdc) 
                            ScaOp_Comp x_cmpEq
                                ScaOp_Identifier QCOL: [cdc] .col6
                                ScaOp_Const TI (smallint, ML = 2) XVAR (smallint, не належить, значення = 4)
                        LogOp_Get TBL: table2 (псевдонім TBL: cdt) 
                        ScaOp_Comp x_cmpEq
                            ScaOp_Identifier QCOL: [cdc] .col1
                            ScaOp_Identifier QCOL: [cdt] .col1
                    LogOp_Get TBL: table3 (псевдонім 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, не належить, значення = 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 substring
                                    ScaOp_Const TI (int, ML = 4) XVAR (int, не належить, значення = 6)
                                    ScaOp_Const TI (int, ML = 4) XVAR (int, не належить, значення = 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план пошуку як для обчислених, так і для не обчислених схем стовпців, коли використовується керівництво плану або підказка.

Чи можемо ми зробити щось інакше, щоб здійснити пошуки

Це те, про що потрібно хвилюватися, лише якщо оптимізатор не знайде план із прийнятними характеристиками продуктивності самостійно.

Усі звичайні інструменти налаштування потенційно застосовні. Наприклад, ви можете розбити запит на більш прості частини, переглянути та вдосконалити доступну індексацію, оновити чи створити нову статистику ... тощо.

Всі ці речі можуть впливати на оцінки кардинальності, шлях коду, що проходить через оптимізатор, і впливати на рішення, що ґрунтуються на витратах, тонкими способами.

Зрештою, ви можете скористатися підказками (або керівництвом щодо плану), але це зазвичай не ідеальне рішення.


Додаткові запитання з коментарів

Я погоджуюся, що найкраще спростити запит тощо, але чи є спосіб (прапор сліду) зробити так, щоб оптимізатор продовжував оптимізацію та досягнув того ж результату?

Ні, не існує жодного сліду, який би здійснював вичерпний пошук, і його ви не хочете. Можливий простір пошуку величезний, і час компіляції, що перевищує вік Всесвіту, не був би добре сприйнятий. Крім того, оптимізатор не знає всіх можливих логічних перетворень (ніхто не робить).

Крім того, навіщо потрібне комплексне розширення, оскільки стовпець зберігається? Чому оптимізатор не може уникнути його розширення, трактувати його як звичайний стовпець і досягти тієї ж відправної точки?

Обчислювані стовпці розширюються (як і представлення), щоб забезпечити додаткові можливості оптимізації. Розширення може відповідати тому, наприклад, збереженому стовпцю або індексу пізніше в процесі, але це відбувається після фіксації початкового замовлення на з'єднання .

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