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들과 더 이상은? 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

최적화가 시작되기 전에 View Anchors 2 개와 내장 함수 6 개 호출에 주목하십시오 newid. 그럼에도 불구하고 많은 사람들은 최적화 프로그램이 확장 된 하위 트리가 원래 단일 참조 객체임을 식별하고 그에 따라 단순화해야한다고 생각합니다. CTE 또는 파생 테이블의 명시적인 구체화를 허용하기위한 몇 가지 Connect 요청 도있었습니다 .

보다 일반적인 구현에서는 옵티마이 CASE저가 성능을 향상시키기 위해 임의의 공통 표현식을 구체화하는 것을 고려하게됩니다 ( 하위 쿼리 를 사용하면 오늘날 문제 가 발생할 수있는 또 다른 예 입니다). Microsoft Research 는 2007 년에 그 당시 의 논문 (PDF)을 발표 했지만 현재까지는 구현되지 않았습니다. 당분간 우리는 테이블 변수 및 임시 테이블과 같은 것을 사용하여 명시 적 구체화로 제한됩니다.

SQLKiwi는 SSIS에서 계획을 세우는 것에 대해 언급했습니다. SQL Server에 대한 올바른 계획을 세우는 데 도움이되는 방법이나 유용한 도구가 있습니까?

이것은 제 생각 에 단지 희망 이었고 계획 지침을 수정한다는 생각을 훨씬 뛰어 넘었습니다. 원칙적으로 쇼 계획 XML을 직접 조작하는 도구를 작성하는 것이 가능하지만, 도구를 사용한 특정 최적화 도구가 없으면 사용자에게는 실망스러운 경험이 될 것입니다 (그리고 개발자가 생각하게됩니다).

이 질문의 특정 맥락에서, 이러한 도구는 여러 소비자가 사용할 수있는 방식으로 CTE 내용을 구체화 할 수 없습니다 (이 경우 두 입력을 교차 조인에 공급하기 위해). 옵티 마이저 및 실행 엔진은 다중 소비자 스풀을 지원하지만 특정 목적으로 만 사용되며이 특정 예제에는 적용 할 수 없습니다.

확실하지는 않지만 쿼리가 계획과 정확히 동일하지 않더라도 RelOps를 따를 수 있습니다 (Nested Loop, Lazy Spool). 예를 들어 CTE에 4와 5를 추가 한 경우 , 여전히 동일한 계획을 사용합니다 (SQL Server 2012 RTM Express에서 테스트 됨).

여기에는 상당한 양의 유연성이 있습니다. XML 계획의 광범위한 형태는 최종 계획에 대한 검색 을 안내 하는 데 사용됩니다 (교환에서 파티션 유형과 같은 많은 속성이 완전히 무시되지만) 일반 검색 규칙도 상당히 완화됩니다. 예를 들어, 비용 고려 사항을 기반으로 한 대안의 조기 제거는 사용 불가능하고, 교차 결합의 명시적인 도입이 허용되며 스칼라 조작은 무시됩니다.

이 깊이 들어가 너무 많은 세부 사항이 있지만, 필터 및 계산 스칼라의 배치는 강제 할 수 없으며, 형태의 술어는 column = value계획이 포함되도록 일반화 X = 1또는 X = @X포함 된 쿼리에 적용 할 수있는 X = 502X = @Y. 이러한 특정 유연성은 자연적인 계획을 찾는 데 큰 도움이 될 수 있습니다.

특정 예에서, Constant Union All은 항상 Constant Scan으로 구현 될 수 있습니다. Union All에 대한 입력 수는 중요하지 않습니다.


3

CTE 발생시 단일 스풀을 재사용 할 수있는 방법은 없습니다 (SQL Server 버전 최대 2012). 자세한 내용은 SQLKiwi의 답변에서 찾을 수 있습니다. 다음 은 CTE를 두 번 구체화하는 두 가지 방법으로, 쿼리의 특성상 피할 수 없습니다. 두 가지 옵션 모두 순 뚜렷한 guid 수는 6입니다.

CTE를 안내하는 계획 에 관한 블로그 에서 Martin의 의견과 Quassnoi 사이트로의 링크는 이 질문에 부분적인 영감을주었습니다. 상관 서브 쿼리를 위해 CTE를 구체화하는 방법을 설명합니다. 상관 쿼리는 상관 관계가 여러 번 평가 될 수 있지만 한 번만 참조됩니다. 질문의 쿼리에는 적용되지 않습니다.

옵션 1-계획 안내서

SQLKiwi의 대답에서 힌트를 얻은 후, 나는 여전히 작업을 수행 할 최소한의 지침으로 가이드를 파싱했습니다 ConstantScan.

;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

2008, R2 및 2012 인스턴스의 왼쪽 안내 열에 대해 traceflag 8649 (강제 병렬 계획)가이 동작을 유발 함을 발견했습니다. 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 개의 guid가 보입니다. 당신은 무엇을 봅니까?
孔夫子

왼쪽에 3 개의 별개의 guid (guid column)가 있으며 각각 3 번 반복됩니다. 오른쪽 (guidb 열)에 9 개의 고유 한 guid가 있으므로 적어도 왼쪽 비트는 원하는 방식으로 작동합니다. 희망을 조금 명확히하기 위해 다른 답변에 이미지를 추가했습니다. 작은 단계. 또한 SQL 2005에서도 6 개의 고유 한 guid (왼쪽에 3, 오른쪽에 3)가 표시됩니다.
wBob

또한 'all'을 제거하면 6 개의 고유 한 guid도 양쪽에 3 개씩 표시됩니다.
wBob

서버 maxdop 1을 사용하여 traceflag 가 작동하지 않게 할 수 있습니다 .
wBob
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.