Создать план руководства по кешированию (ленивая шпуля) результата 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, на которые ссылаются несколько раз, чтобы преодолеть некоторые причуды SQL Server CTE.

孔夫子
источник

Ответы:

14

Есть ли ЛЮБОЙ способ заставить результат получить ровно 3 различных руководства и не более? Я надеюсь, что смогу лучше ответить на вопросы в будущем, включив руководства плана с запросами типа CTE, на которые ссылаются несколько раз, чтобы преодолеть некоторые причуды SQL Server CTE.

Не сегодня. Нерекурсивные общие табличные выражения (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до начала оптимизации. Тем не менее, многие считают, что оптимизатор должен быть в состоянии определить, что расширенные поддеревья изначально были единым объектом, на который ссылаются, и соответственно упростить. Также было несколько запросов Connect, чтобы разрешить явную материализацию CTE или производной таблицы.

В более общей реализации оптимизатор может рассмотреть возможность материализации произвольных общих выражений для повышения производительности ( CASEс помощью подзапроса это еще один пример, где проблемы могут возникнуть сегодня). Microsoft Research опубликовала документ (PDF) по этому вопросу еще в 2007 году, хотя до сих пор он не реализован. В настоящее время мы ограничены явной материализацией с использованием таких вещей, как переменные таблиц и временные таблицы.

SQLKiwi упомянул составление планов в SSIS, есть ли способ или полезный инструмент, помогающий составить хороший план для SQL Server?

Это было просто желаемое размышление с моей стороны, и оно выходило далеко за рамки идеи изменения руководств плана. В принципе, можно написать инструмент для непосредственного управления XML-планом show plan, но без специального инструментария оптимизатора использование этого инструмента, вероятно, будет разочаровывающим опытом для пользователя (и разработчик придет к этому думать).

В конкретном контексте этого вопроса такой инструмент по-прежнему не сможет материализовать содержимое 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 всегда может быть реализована как постоянное сканирование; количество входов в Союз All не имеет значения.

Пол Уайт говорит, что GoFundMonica
источник
3

Невозможно (версии SQL Server до 2012 г.) повторно использовать одну катушку для обоих вхождений CTE. Подробности можно найти в ответе SQLKiwi. Далее ниже приведены два способа материализации CTE дважды, что неизбежно для характера запроса. Оба варианта приводят к чистому различному количеству guid 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". Возможно, для очень простого запроса вы могли бы просто справиться с этим, но это растянуто, возможно, даже пустая трата времени. Вы можете сказать, что заняты работой.

Если я создаю план для подсказки или руководства плана USE PLAN, у меня есть пара подходов. Например, я мог бы удалить записи из таблиц (например, в копии БД), чтобы повлиять на статистику и призвать оптимизатор принять другое решение. Я также использовал переменные таблицы вместо всей таблицы в запросе, поэтому оптимизатор считает, что каждая таблица содержит 1 запись. Затем в сгенерированном плане замените все переменные таблицы на исходные имена таблиц и замените их как план. Другим вариантом будет использование опции WITH STATS_STREAM в UPDATE STATISTICS для подмены статистики, которая используется при клонировании копий баз данных, содержащих только статистику, например

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

В прошлом я потратил некоторое время, работая с планами выполнения XML, и обнаружил, что в итоге SQL просто говорит: «Я не использую это» и в любом случае выполняет запрос так, как хочет.

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

Не пытаясь быть неконструктивным, просто мое мнение - надеюсь, это поможет.

wBob
источник
Серьезно, XML-планы игнорируются?! Я думал, в этом весь смысл? Если они недействительны, он должен бросить.
crokusek
Я имел в виду событие Plan Guide Unsuccessful.
wBob
2

Есть ЛЮБОЙ способ ...

Наконец в SQL 2016 CTP 3.0 есть способ, вроде:)

Используя флаг трассировки и Расширенные события, подробно описанные здесь Дмитрием Пилугиным , вы можете (несколько произвольно) найти три уникальных руководства из промежуточных этапов выполнения запроса.

NB. Этот код НЕ предназначен для производственного или серьезного использования в отношении форсирования плана 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), просто для удовольствия.

wBob
источник
1

Я обнаружил, что traceflag 8649 (план принудительного параллелизма) вызвал такое поведение для левой колонки guid в моих экземплярах 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 )

Либо с помощью подсказки, с помощью руководства плана, включая подсказку, либо с помощью плана, сгенерированного запросом с подсказкой в ​​USE PLAN и т. Д., Все работало. новый

wBob
источник
Спасибо за попытку снова. Запрос не выглядит по-другому с этим флагом трассировки 2008/2012 или без него. Не совсем уверен, что это мои экземпляры SQL Server или что вы пытаетесь показать. Я все еще вижу 18 гидов. Что ты видишь?
孔夫子
3 отдельных направляющих с левой стороны (направляющая колонка), каждая из которых повторяется три раза. 9 уникальных направляющих на правой стороне (столбец guidb), так что, по крайней мере, левый бит ведет себя так, как вы хотите, лол. Я добавил изображение к другому ответу, чтобы немного уточнить. Маленькие шаги Следует также отметить, что в SQL 2005 я получаю 6 уникальных направляющих, 3 слева и 3 справа.
wBob
Также только что заметил, что удаление 'all' также дает 6 уникальных направляющих, по 3 с каждой стороны.
wBob
Может заставить traceflag не работать , имея сервер maxdop 1.
wBob