web-dev-qa-db-ja.com

CTE結果をキャッシュする(遅延スプール)ための計画ガイドを作成する

私は通常、最初に正しいプランを使用するクエリを作成し、それを使用しない同様のクエリにコピーして、プランガイドを作成します。ただし、クエリが完全に同じでない場合は特に、それが難しい場合があります。ゼロから計画ガイドを作成する正しい方法は何ですか?

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;

[〜#〜] any [〜#〜]を使用して、結果に正確に3つの異なるguidsを付け、もっと? SQL Serverの一部のCTEの癖を克服するために複数回参照されるCTEタイプのクエリを含むプランガイドを含めることで、将来、より適切に質問に答えられるようにしたいと思っています。

19
孔夫子

結果を正確に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つのビューアンカーと組み込み関数newidへのsix呼び出しに注意してください。それにもかかわらず、多くの人々は、オプティマイザは展開されたサブツリーが元々単一の参照オブジェクトであったことを識別し、それに応じて単純化できるべきであると考えています。 CTEまたは派生テーブルの明示的な実体化を可能にする 接続要求 もいくつかあります。

より一般的な実装では、パフォーマンスを向上させるためにオプティマイザに任意の一般的な式の具体化を検討させます(サブクエリを使用したCASEは、今日 problems が発生する可能性がある別の例です)。 Microsoft Research 論文を発表しました (PDF)2007年のことですが、現時点ではまだ実装されていません。当面は、テーブル変数や一時テーブルなどを使用した明示的な具体化に限定されます。

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

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

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

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

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

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

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

14
Paul White 9

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

MartinのコメントからQuassnoiのサイトへのリンク CTEを導く計画についてのブログ へのリンクは、この質問の部分的なインスピレーションでした。これは、相関サブクエリの目的でCTEを具体化する方法を説明します。相関サブクエリは複数回評価される可能性がありますが、1回だけ参照されます。それは質問のクエリには適用されません。

オプション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;
3
孔夫子

ありますか[〜#〜]任意[〜#〜]方法...

最後にSQL 2016 CTP 3.0には、一種の方法があります。

Dmitry Pilugin here で詳述されているトレースフラグと拡張イベントを使用すると、クエリ実行の中間段階から3つの一意のGUIDを(任意に)釣り出すことができます。

注:このコードは、[〜#〜] [〜#〜](〜#〜]ではなく、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)でテスト済み。

2
wBob

真剣に考えると、XML実行プランを最初から切り取ることはできません。 SSISを使用してそれらを作成することはSFです。はい、それはすべてXMLですが、それらは異なる宇宙のものです。 topic に関するPaulのブログを見て、彼は「SSISが許可する方法の大部分は...」と言っているので、おそらく誤解しているのではないでしょうか。私は彼が「SSISを使用して計画を作成する」と言っているのではなく、「ドラッグアンドドロップインターフェイスlikeSSIS "。たぶん、非常に単純なクエリの場合、これを管理することはできますが、時間の浪費になる可能性もあります。あなたが言うかもしれない忙しい仕事。

USE PLANヒントまたはプランガイドの計画を作成している場合、いくつかのアプローチがあります。たとえば、統計情報に影響を与え、オプティマイザが別の決定を下すように促すために、レコードをテーブル(たとえば、dbのコピー)から削除する場合があります。クエリ内のすべてのテーブルの代わりにテーブル変数も使用したので、オプティマイザはすべてのテーブルに1つのレコードが含まれていると見なします。次に、生成されたプランで、すべてのテーブル変数を元のテーブル名に置き換え、プランとして入れ替えます。もう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を使用してその結果を取得できることはご存じでしょうが、それはあなたの目的ではないと思います。 正解の答えは、実際には次のとおりです。一時テーブルを使用します。私はそれを支持します:)正解ではありません。「独自のカスタムXML実行プランを数日でさえ数日かけて、オプティマイザーをだましてCTEの遅延スプールを実行させようとしても、それでも機能しない可能性がありますcleverしかし、維持することも不可能です。 ".

そこでは非建設的になろうとはせず、ただ私の意見です-それが役立つことを願っています。

2
wBob

トレースフラグ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

1
wBob