Створіть керівництво по плану кеш-результату CTE (ледачий золотник)


19

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

SQLKiwi згадав про складання планів у SSIS, чи є спосіб чи корисний інструмент, який допоможе скласти хороший план для SQL Server?

Конкретний екземпляр, про який йде мова, це CTE: SQLFiddle

with cte(guid,other) as (
  select newid(),1 union all
  select newid(),2 union all
  select newid(),3)
select a.guid, a.other, b.guid guidb, b.other otherb
from cte a
cross join cte b
order by a.other, b.other;

Чи існує ЯКЩО-небудь спосіб зробити результат придумати рівно 3 чіткі guidі не більше? Я сподіваюся, що зможу краще відповісти на запитання в майбутньому, включивши посібники щодо плану з запитами типу CTE, на які посилається багато разів, щоб подолати деякі випробування CTE на SQL Server.


Відповіді:


14

Чи існує ЯКЩО-небудь спосіб зробити результат, щоб створити рівно 3 чіткі посібники і не більше? Я сподіваюся, що зможу краще відповісти на питання в майбутньому, включивши посібники щодо плану із запитами типу CTE, на які посилаються багато разів, щоб подолати деякі випробування CTE на SQL Server.

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

LogOp_OrderByCOL: Union1007 ASC COL: Union1015 ASC 
    LogOp_Project COL: Union1006 COL: Union1007 COL: Union1014 COL: Union1015
        LogOp_Join
            LogOp_ViewAnchor
                LogOp_UnionAll
                    LogOp_Project ScaOp_Intrinsic newid, ScaOp_Const
                    LogOp_Project ScaOp_Intrinsic newid, ScaOp_Const
                    LogOp_Project ScaOp_Intrinsic newid, ScaOp_Const

            LogOp_ViewAnchor
                LogOp_UnionAll
                    LogOp_Project ScaOp_Intrinsic newid, ScaOp_Const
                    LogOp_Project ScaOp_Intrinsic newid, ScaOp_Const
                    LogOp_Project ScaOp_Intrinsic newid, ScaOp_Const

Зауважте два прив’язки для перегляду та шість викликів до внутрішньої функціїnewid перед тим, як почати оптимізацію. Тим не менш, багато людей вважають, що оптимізатору слід було б визначити, що розширені під деревами спочатку були єдиним посилається об'єктом і відповідно спрощувати його. Також було кілька запитів на підключення щоб дозволити явну матеріалізацію CTE або похідної таблиці.

Більш загальна реалізація дозволила б оптимізатору розглянути можливість матеріалізації довільних загальних виразів для підвищення продуктивності ( CASEз підзапитом - ще один приклад, коли проблеми можуть виникнути сьогодні). Корпорація Microsoft Research опублікувала документ (PDF) про це ще в 2007 році, хоча він досі залишається без змін. Наразі ми обмежені явною матеріалізацією, використовуючи такі речі, як змінні таблиці та тимчасові таблиці.

SQLKiwi згадав про складання планів у SSIS, чи є спосіб чи корисний інструмент, який допоможе скласти хороший план для SQL Server?

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

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

Хоча я не впевнений, у мене досить сильна думка про те, що за RelOps можна дотримуватися (Nested Loop, Lazy Spool), навіть якщо запит не такий, як план - наприклад, якщо ви додали 4 та 5 до CTE , він все ще продовжує використовувати той самий план (здавалося б - протестований на SQL Server 2012 RTM Express).

Тут є розумна кількість гнучкості. Широка форма плану XML використовується для керування пошуком остаточного плану (хоча багато атрибутів повністю ігноруються, наприклад, тип розподілу на біржах), а звичайні правила пошуку також значно послаблені. Наприклад, раннє обрізання альтернатив на основі міркувань витрат вимкнено, дозволено чітке введення перехресних з'єднань, а скалярні операції ігноруються.

Занадто багато деталей, щоб заглибитись, але розміщення фільтрів та обчислювальних скалярів не може бути вимушеним, і предикати форми column = valueузагальнені, щоб план, що містить X = 1або X = @Xможе бути застосований до запиту, що містить X = 502абоX = @Y . Ця особлива гнучкість може сильно допомогти у пошуку природного плану, який слід застосовувати.

У конкретному прикладі константа Union All завжди може бути реалізована як постійне сканування; кількість входів до Союзу Все не має значення.


3

Немає можливості (версії SQL Server до 2012 року) повторно використовувати одну котушку для обох випадків роботи CTE. Деталі можна знайти у відповіді SQLKiwi. Далі наведено два способи оформити CTE двічі, що неминуче за характером запиту. Обидва варіанти призводять до чистої чіткої орієнтації 6.

Часткове натхнення для цього питання було посиланням із коментаря Мартіна до сайту Quassnoi в блозі про план керівництва CTE . Він описує спосіб матеріалізації CTE з метою співвіднесеного підзапиту, на який посилається лише один раз, хоча кореляція може спричинити його багаторазову оцінку. Це не стосується запиту у питанні.

Варіант 1 - Посібник з плану

Беручи підказки з відповіді SQLKiwi, я склав керівництво до чистого мінімуму, який все одно буде виконувати цю роботу, наприклад, ConstantScanлише у вузлах перелічено 2 скалярних оператора, які можуть достатньо розширитися на будь-яке число.

;with cte(guid,other) as (
  select newid(),1 union all
  select newid(),2 union all
  select newid(),3)
select a.guid, a.other, b.guid guidb, b.other otherb
from cte a
cross join cte b
order by a.other, b.other
OPTION(USE PLAN
N'<?xml version="1.0" encoding="utf-16"?>
<ShowPlanXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Version="1.2" Build="11.0.2100.60" xmlns="http://schemas.microsoft.com/sqlserver/2004/07/showplan">
  <BatchSequence>
    <Batch>
      <Statements>
        <StmtSimple StatementCompId="1" StatementEstRows="1600" StatementId="1" StatementOptmLevel="FULL" StatementOptmEarlyAbortReason="GoodEnoughPlanFound" StatementSubTreeCost="0.0444433" StatementText="with cte(guid,other) as (&#xD;&#xA;  select newid(),1 union all&#xD;&#xA;  select newid(),2 union all&#xD;&#xA;  select newid(),3&#xD;&#xA;select a.guid, a.other, b.guid guidb, b.other otherb&#xD;&#xA;from cte a&#xD;&#xA;cross join cte b&#xD;&#xA;order by a.other, b.other;&#xD;&#xA;" StatementType="SELECT" QueryHash="0x43D93EF17C8E55DD" QueryPlanHash="0xF8E3B336792D84" RetrievedFromCache="true">
          <StatementSetOptions ANSI_NULLS="true" ANSI_PADDING="true" ANSI_WARNINGS="true" ARITHABORT="true" CONCAT_NULL_YIELDS_NULL="true" NUMERIC_ROUNDABORT="false" QUOTED_IDENTIFIER="true" />
          <QueryPlan NonParallelPlanReason="EstimatedDOPIsOne" CachedPlanSize="96" CompileTime="13" CompileCPU="13" CompileMemory="1152">
            <MemoryGrantInfo SerialRequiredMemory="0" SerialDesiredMemory="0" />
            <OptimizerHardwareDependentProperties EstimatedAvailableMemoryGrant="157240" EstimatedPagesCached="1420" EstimatedAvailableDegreeOfParallelism="1" />
            <RelOp AvgRowSize="47" EstimateCPU="0.006688" EstimateIO="0" EstimateRebinds="0" EstimateRewinds="0" EstimatedExecutionMode="Row" EstimateRows="1600" LogicalOp="Inner Join" NodeId="0" Parallel="false" PhysicalOp="Nested Loops" EstimatedTotalSubtreeCost="0.0444433">
              <OutputList>
                <ColumnReference Column="Union1163" />
              </OutputList>
              <Warnings NoJoinPredicate="true" />
              <NestedLoops Optimized="false">
                <RelOp AvgRowSize="27" EstimateCPU="0.000432115" EstimateIO="0.0112613" EstimateRebinds="0" EstimateRewinds="0" EstimatedExecutionMode="Row" EstimateRows="40" LogicalOp="Sort" NodeId="1" Parallel="false" PhysicalOp="Sort" EstimatedTotalSubtreeCost="0.0117335">
                  <OutputList>
                    <ColumnReference Column="Union1080" />
                    <ColumnReference Column="Union1081" />
                  </OutputList>
                  <MemoryFractions Input="0" Output="0" />
                  <Sort Distinct="false">
                    <OrderBy>
                      <OrderByColumn Ascending="true">
                        <ColumnReference Column="Union1081" />
                      </OrderByColumn>
                    </OrderBy>
                    <RelOp AvgRowSize="27" EstimateCPU="4.0157E-05" EstimateIO="0" EstimateRebinds="0" EstimateRewinds="0" EstimatedExecutionMode="Row" EstimateRows="40" LogicalOp="Constant Scan" NodeId="2" Parallel="false" PhysicalOp="Constant Scan" EstimatedTotalSubtreeCost="4.0157E-05">
                      <OutputList>
                        <ColumnReference Column="Union1080" />
                        <ColumnReference Column="Union1081" />
                      </OutputList>
                      <ConstantScan>
                        <Values>
                          <Row>
                            <ScalarOperator ScalarString="newid()">
                              <Intrinsic FunctionName="newid" />
                            </ScalarOperator>
                            <ScalarOperator ScalarString="(1)">
                              <Const ConstValue="(1)" />
                            </ScalarOperator>
                          </Row>
                          <Row>
                            <ScalarOperator ScalarString="newid()">
                              <Intrinsic FunctionName="newid" />
                            </ScalarOperator>
                            <ScalarOperator ScalarString="(2)">
                              <Const ConstValue="(2)" />
                            </ScalarOperator>
                          </Row>
                        </Values>
                      </ConstantScan>
                    </RelOp>
                  </Sort>
                </RelOp>
                <RelOp AvgRowSize="27" EstimateCPU="0.0001074" EstimateIO="0.01" EstimateRebinds="0" EstimateRewinds="39" EstimatedExecutionMode="Row" EstimateRows="40" LogicalOp="Lazy Spool" NodeId="83" Parallel="false" PhysicalOp="Table Spool" EstimatedTotalSubtreeCost="0.0260217">
                  <OutputList>
                    <ColumnReference Column="Union1162" />
                    <ColumnReference Column="Union1163" />
                  </OutputList>
                  <Spool>
                    <RelOp AvgRowSize="27" EstimateCPU="0.000432115" EstimateIO="0.0112613" EstimateRebinds="0" EstimateRewinds="0" EstimatedExecutionMode="Row" EstimateRows="40" LogicalOp="Sort" NodeId="84" Parallel="false" PhysicalOp="Sort" EstimatedTotalSubtreeCost="0.0117335">
                      <OutputList>
                        <ColumnReference Column="Union1162" />
                        <ColumnReference Column="Union1163" />
                      </OutputList>
                      <MemoryFractions Input="0" Output="0" />
                      <Sort Distinct="false">
                        <OrderBy>
                          <OrderByColumn Ascending="true">
                            <ColumnReference Column="Union1163" />
                          </OrderByColumn>
                        </OrderBy>
                        <RelOp AvgRowSize="27" EstimateCPU="4.0157E-05" EstimateIO="0" EstimateRebinds="0" EstimateRewinds="0" EstimatedExecutionMode="Row" EstimateRows="40" LogicalOp="Constant Scan" NodeId="85" Parallel="false" PhysicalOp="Constant Scan" EstimatedTotalSubtreeCost="4.0157E-05">
                          <OutputList>
                            <ColumnReference Column="Union1162" />
                            <ColumnReference Column="Union1163" />
                          </OutputList>
                          <ConstantScan>
                            <Values>
                              <Row>
                                <ScalarOperator ScalarString="newid()">
                                  <Intrinsic FunctionName="newid" />
                                </ScalarOperator>
                                <ScalarOperator ScalarString="(1)">
                                  <Const ConstValue="(1)" />
                                </ScalarOperator>
                              </Row>
                              <Row>
                                <ScalarOperator ScalarString="newid()">
                                  <Intrinsic FunctionName="newid" />
                                </ScalarOperator>
                                <ScalarOperator ScalarString="(2)">
                                  <Const ConstValue="(2)" />
                                </ScalarOperator>
                              </Row>
                            </Values>
                          </ConstantScan>
                        </RelOp>
                      </Sort>
                    </RelOp>
                  </Spool>
                </RelOp>
              </NestedLoops>
            </RelOp>
          </QueryPlan>
        </StmtSimple>
      </Statements>
    </Batch>
  </BatchSequence>
</ShowPlanXML>'
);

Варіант 2 - Віддалене сканування

Збільшуючи витрати на запит та вводячи віддалене сканування, результат матеріалізується.

with cte(guid,other) as (
  select *
  from OPENQUERY([TESTSQL\V2012], '
  select newid(),1 union all
  select newid(),2 union all
  select newid(),3') x)
select a.guid, a.other, b.guid guidb, b.other otherb
from cte a
cross join cte b
order by a.other, b.other;

2

Якщо говорити серйозно, ви не можете зрізати плани з виконання xml з нуля. Створення їх за допомогою SSIS - наукова фантастика. Так, це все XML, але вони є з різних всесвітів. Переглядаючи блог Павла на цю тему , він говорить "набагато так, як дозволяє SSIS ...", так що, можливо, ви неправильно зрозуміли? Я не думаю, що він говорить "використовувати SSIS для створення планів", а "не було б чудово, щоб можна було створювати плани за допомогою інтерфейсу перетягування, як SSIS". Можливо, для дуже простого запиту ви могли б просто впоратися з цим, але це розтягнення, можливо, навіть марна трата часу. Ви можете сказати, зайнята робота.

Якщо я створюю план для підказки щодо ПЛАНУ ВИКОРИСТАННЯ або керівництва щодо плану, у мене є кілька підходів. Наприклад, я можу видалити записи з таблиць (наприклад, на копії db), щоб вплинути на статистику та спонукати оптимізатора прийняти інше рішення. Я також використовував змінні таблиці замість усієї таблиці в запиті, тому оптимізатор вважає, що кожна таблиця містить 1 запис. Потім в генерованому плані замініть всі змінні таблиці оригінальними іменами таблиці та замініть їх як план. Іншим варіантом було б використання опції UP STATE_STREAM ОНОВЛЕННЯ СТАТИСТИКИ для підробки статистики підробки, що є методом, який використовується при клонуванні копій баз даних лише для статистики, наприклад

UPDATE STATISTICS 
    [dbo].[yourTable]([PK_yourTable]) 
WITH 
    STATS_STREAM = 0x0100etc, 
    ROWCOUNT = 10000, 
    PAGECOUNT = 93

У минулому я провів деякий час, займаючись планами виконання xml, і виявив, що врешті-решт, SQL просто переходить "я цього не використовую" і виконує запит, як цього хочеться.

Для вашого конкретного прикладу я впевнений, що ви знаєте, що ви можете використати встановлені рядки 3 або TOP 3 у запиті, щоб отримати такий результат, але я думаю, що це не ваша суть. Правильний відповідь буде дійсно: використовувати тимчасову таблицю. Я б заперечив:) Неправильною відповіддю було б "витратити години, навіть дні на різання власного користувальницького плану виконання XML, де ви намагаєтесь обдурити оптимізатор зробити ледачу котушку для CTE, яка може взагалі не працювати, виглядатиме розумно але також було б неможливо підтримувати ".

Не намагаючись бути там неконструктивним, просто моя думка - сподівання, що допомагає.


Серйозно, XML-плани невідомі?!, Я думав, що це вся справа? Якщо вони недійсні, його слід кинути.
crokusek

Я мав на увазі невдалу подію в Посібнику плану.
wBob

2

Чи існує якийсь спосіб ...

Нарешті, у SQL 2016 CTP 3.0 є спосіб, вид:)

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

Примітка Цей код НЕ призначений для виробництва або для серйозного використання щодо примусового плану CTE, просто легковажним поглядом на новий прапор слідів і іншим способом здійснення дій:

-- Configure the XEvents session; with ring buffer target so we can collect it
CREATE EVENT SESSION [query_trace_column_values] ON SERVER 
ADD EVENT sqlserver.query_trace_column_values
ADD TARGET package0.ring_buffer( SET max_memory = 2048 )
WITH ( MAX_MEMORY = 4096 KB, EVENT_RETENTION_MODE = ALLOW_SINGLE_EVENT_LOSS, MAX_DISPATCH_LATENCY = 30 SECONDS, MAX_EVENT_SIZE = 0 KB, MEMORY_PARTITION_MODE = NONE, TRACK_CAUSALITY = OFF , STARTUP_STATE = OFF )
GO

-- Start the session
ALTER EVENT SESSION [query_trace_column_values] ON SERVER
STATE = START;
GO

-- Run the query, including traceflag
DBCC TRACEON(2486);
SET STATISTICS XML ON;
GO

-- Original query
;with cte(guid,other) as (
  select newid(),1 union all
  select newid(),2 union all
  select newid(),3)
select a.guid, a.other, b.guid guidb, b.other otherb
from cte a
cross join cte b
order by a.other, b.other
option ( recompile )
go

SET STATISTICS XML OFF;
DBCC TRACEOFF(2486);
GO

DECLARE @target_data XML

SELECT @target_data = CAST( target_data AS XML )
FROM sys.dm_xe_sessions AS s 
    INNER JOIN sys.dm_xe_session_targets AS t ON t.event_session_address = s.address
WHERE s.name = 'query_trace_column_values'


--SELECT @target_data td

-- Arbitrarily fish out 3 unique guids from intermediate stage of the query as collected by XEvent session
;WITH cte AS
(
SELECT
    n.c.value('(data[@name = "row_id"]/value/text())[1]', 'int') row_id,
    n.c.value('(data[@name = "column_value"]/value/text())[1]', 'char(36)') [guid]
FROM @target_data.nodes('//event[data[@name="column_id"]/value[. = 1]][data[@name="row_number"]/value[. < 4]][data[@name="node_name"]/value[. = "Nested Loops"]]') n(c)
)
SELECT *
FROM cte a
    CROSS JOIN cte b
GO

-- Stop the session
ALTER EVENT SESSION [query_trace_column_values] ON SERVER
STATE = STOP;
GO

-- Drop the session
IF EXISTS ( select * from sys.server_event_sessions where name = 'query_trace_column_values' )
DROP EVENT SESSION [query_trace_column_values] ON SERVER 
GO

Тестований на версії (CTP3.2) - 13.0.900.73 (x64), просто для розваги.


1

Я виявив, що traceflag 8649 (паралельний план сили) викликав цю поведінку для лівого стовпчика орієнтування на мої випадки 2008, R2 та 2012. Мені не потрібно було використовувати прапор у SQL 2005, де CTE поводився правильно. Я спробував використовувати план, сформований у SQL 2005 у вищих випадках, але він не підтвердив.

with cte(guid,other) as (
  select newid(),1 union all
  select newid(),2 union all
  select newid(),3)
select a.guid, a.other, b.guid guidb, b.other otherb
from cte a
cross join cte b
order by a.other, b.other
option ( querytraceon 8649 )

Або за допомогою підказки, використовуючи керівництво щодо плану, включаючи підказку, або використовуючи план, згенерований запитом, з підказкою в ПЛАНУ ВИКОРИСТАННЯ тощо. cte newid


Дякуємо за повторну спробу. Запит не виглядає інакше з цим прапором слідів 2008/2012 або без нього. Не дуже впевнений, це мої екземпляри SQL Server чи те, що ви намагаєтесь показати. Я все ще бачу 18 путівників. Що ти бачиш?
孔夫子

3 чіткі направляючі з лівого боку (стовпчик направляючих), кожен з яких повторюється три рази. 9 унікальних посібників з правого боку (стовпчик орієнтирів), тож принаймні лівий біт поводиться так, як ви хочете, ха-ха. Я додав зображення до іншої відповіді, щоб сподіватися трохи прояснити. Маленькі кроки. Я також повинен зазначити, що в SQL 2005 я отримую 6 унікальних посібників, 3 зліва, 3 справа.
wBob

Також щойно зауважив, що при видаленні "всіх" отримують і 6 унікальних посібників, по 3 в кожну сторону.
wBob

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