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つの異なる思い付くようにする方法guidsおよびこれ以上は?いくつかのSQL Server CTEの癖を克服するために複数回参照されるCTEタイプのクエリを含むプランガイドを含めることで、今後より良い質問に答えられることを望んでいます。


回答:


14

結果を正確に3つの異なるGUIDだけで作成する方法はありますか?いくつかのSQL Server CTEの癖を克服するために複数回参照される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

最適化を開始する前に、2つのビューアンカーと組み込み関数の6つの呼び出しに注目してくださいnewid。それにもかかわらず、多くの人々は、オプティマイザは、展開されたサブツリーが元々単一の参照オブジェクトであることを識別し、それに応じて単純化できるべきであると考えています。また、CTEまたは派生テーブルの明示的なマテリアライズを許可するために、いくつかのConnect要求がありました。

より一般的な実装では、オプティマイザがパフォーマンスを改善するために任意の共通式を具体化することを検討します(CASEサブクエリは、今日問題が発生する可能性がある別の例です)。Microsoft Research は、 2007年にそれに関する論文(PDF)を発行しましたが、現在まで未実装のままです。とりあえず、テーブル変数や一時テーブルなどを使用した明示的なマテリアライゼーションに制限されています。

SQLKiwiはSSISで計画を作成することについて言及しましたが、SQL Serverの適切な計画のレイアウトを支援する方法または有用なツールはありますか?

これは私の側の希望的観測であり、計画ガイドを修正するという考えをはるかに超えていました。原則として、ショープランのXMLを直接操作するツールを作成することは可能ですが、このツールを使用した特定のオプティマイザーインストルメントがないと、ユーザーにとってはイライラするような経験になります(そして開発者はそれを考えるようになります)。

この質問の特定のコンテキストでは、このようなツールは、複数のコンシューマーが使用できる方法でCTEコンテンツを具体化することができません(この場合、両方の入力をクロス結合にフィードします)。オプティマイザーと実行エンジンは、複数の消費者のスプールをサポートしますが、特定の目的のためだけです。この特定の例に適用することはできません。

確かではありませんが、クエリが計画と正確に同じではない場合でも、RelOps(ネストループ、レイジースプール)をたどることができるかなり強い予感があります-たとえば、CTEに4と5を追加した場合、引き続き同じプランを使用し続けます(一見-SQL Server 2012 RTM Expressでテスト済み)。

ここにはかなりの柔軟性があります。XMLプランの広範な形状は、最終プランの検索をガイドするために使用されます(ただし、多くの属性は完全に無視されます(例:交換のパーティションタイプ))。通常の検索ルールも大幅に緩和されます。たとえば、コストの考慮に基づいた代替の早期プルーニングは無効になり、クロス結合の明示的な導入が許可され、スカラー操作は無視されます。

詳細が多すぎて詳細に説明することはできませんが、フィルターおよび計算スカラーの配置を強制することはできず、フォームの述部column = valueは一般化されているため、X = 1またはX = @Xを含むクエリを含むX = 502またはクエリに適用できますX = @Y。この特定の柔軟性は、強制する自然な計画を見つけるのに非常に役立ちます。

特定の例では、定数Union Allは常に定数スキャンとして実装できます。Union Allへの入力の数は重要ではありません。


3

CTEの両方の発生に対して単一のスプールを再利用する方法(2012年までのSQL Serverバージョン)はありません。詳細については、SQLKiwiの回答をご覧ください。さらに、CTEを2回具体化する2つの方法がありますが、これはクエリの性質上避けられません。どちらのオプションでも、6の正味の別個のGUIDカウントが得られます。

MartinのコメントからCTE導く計画についてブログの Quassnoiのサイトへのリンクは、この質問の部分的なインスピレーションでした。相関サブクエリの目的で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ですが、それらは異なる宇宙からのものです。そのトピックに関するPaulのブログを見て、彼は「SSISが許可する方法で...」と言っているので、おそらくあなたは誤解していますか?彼は「SSISを使用して計画を作成する」と言っているのではなく、「SSISのようなドラッグアンドドロップインターフェイスを使用して計画を作成できるのは素晴らしいことではない」と思います。たぶん、非常に単純なクエリの場合、これをほぼ管理できますが、それはストレッチであり、おそらく時間の無駄ですらあります。あなたが言うかもしれない忙しい仕事。

USE PLANヒントまたはプランガイドのプランを作成する場合、いくつかのアプローチがあります。たとえば、テーブルからレコードを削除して(dbのコピーなど)統計に影響を与え、オプティマイザーが別の決定を下すように勧めます。また、クエリ内のすべてのテーブルの代わりにテーブル変数を使用したため、オプティマイザーはすべてのテーブルに1つのレコードが含まれていると見なします。次に、生成されたプランで、すべてのテーブル変数を元のテーブル名に置き換えて、プランとしてスワップインします。別のオプションは、UPDATE STATISTICSのWITH STATS_STREAMオプションを使用して統計をスプーフィングすることです。これは、データベースの統計のみのコピーを複製するときに使用される方法です。

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

私は過去にxml実行プランをいじくり回していましたが、結局、SQLは「それを使用していません」になり、とにかくクエリを実行することがわかりました。

具体的な例として、クエリでset rowcount 3またはTOP 3を使用してその結果を取得できることを知っていると思いますが、それはあなたのポイントではないと思います。正しい答えは本当に次のようになります。一時テーブルを使用します。未正しい答えは、あなたも、とにかく動作しない場合がありますCTEのための怠惰なスプールを行うにoptimzerをだまししようとすると、独自のカスタムXMLの実行計画をカットアップ支出も、時間の日には、なります」だろう):私はそれをupvoteだろう賢いです維持することも不可能です」。

そこに非建設的になろうとするのではなく、ただ私の意見-それが助けてくれることを願っています。


真剣に、XMLプランは無視できますか?!、それが全体のポイントだと思いましたか?無効な場合はスローする必要があります。
crokusek

プランガイドの失敗イベントについて言及していました。
wBob

2

そこにあるいかなる方法は...

最後に、SQL 2016 CTP 3.0には、次のような方法があります。

ここでDmitry Piluginが詳しく説明しているトレースフラグと拡張イベントを使用すると、クエリ実行の中間段階から3つの一意のGUIDを(多少勝手に)抽出できます。

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)でテスト済み。


1

トレースフラグ8649(強制並列プラン)が、2008、R2、および2012インスタンスの左側のGUID列にこの動作を誘発することがわかりました。CTEが正しく動作するSQL 2005でフラグを使用する必要はありませんでした。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などでヒントを使用してクエリで生成されたプランを使用するかのいずれかがすべて機能しました。 cte newid


もう一度お試しいただきありがとうございます。クエリは、2008/2012のトレースフラグの有無にかかわらず変わりません。それが私のSQL Serverインスタンスなのか、それとも何を表示しようとしているのか本当にわかりません。まだ18個のガイドが表示されます。何が見えますか?
孔夫子

左側(ガイド列)に3つの個別のガイドがあり、それぞれ3回繰り返されます。右側(guidb列)に9個のユニークなガイドがあるため、少なくとも左のビットはあなたが望むように振る舞います。私は他の回答に画像を追加して、うまくいけば少し明確にしています。小さなステップ。また、SQL 2005では、左側に3つ、右側に3つ、6つの一意のGUIDがあります。
wBob

また、「すべて」を削除すると、6つの一意のGUIDも取得されることに注意してください。
-wBob

トレースフラグ作ることができない作業をサーバーMAXDOP 1を持っていることによって
wBob
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.