Buat panduan rencana untuk men-cache (malas spool) hasil CTE

19

Saya biasanya membuat panduan rencana dengan terlebih dahulu membangun kueri yang menggunakan rencana yang benar, dan menyalinnya ke kueri serupa yang tidak. Namun, itu kadang rumit, terutama jika kuerinya tidak persis sama. Apa cara yang benar untuk membuat panduan rencana dari awal?

SQLKiwi telah menyebutkan menyusun rencana dalam SSIS, apakah ada cara atau alat yang berguna untuk membantu dalam menyusun rencana yang baik untuk SQL Server?

Contoh spesifik yang dimaksud adalah CTE: SQLFiddle ini

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;

Apakah ada cara APA SAJA untuk membuat hasil dengan tepat 3 berbeda guiddan tidak lebih? Saya berharap dapat menjawab pertanyaan dengan lebih baik di masa depan dengan memasukkan panduan paket dengan kueri tipe CTE yang direferensikan beberapa kali untuk mengatasi beberapa quirks CTE SQL Server.

孔夫子
sumber

Jawaban:

14

Apakah ada cara APA PUN untuk membuat hasilnya dengan tepat 3 panduan berbeda dan tidak lebih? Saya berharap dapat menjawab pertanyaan dengan lebih baik di masa depan dengan memasukkan panduan paket dengan kueri tipe CTE yang direferensikan beberapa kali untuk mengatasi beberapa quirks CTE SQL Server.

Tidak hari ini. Ekspresi tabel umum non-rekursif (CTE) diperlakukan sebagai definisi tampilan in-line dan diperluas ke pohon kueri logis di setiap tempat yang direferensikan (seperti definisi tampilan biasa) sebelum optimasi. Pohon logis untuk permintaan Anda adalah:

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

Perhatikan dua View Anchors dan enam panggilan ke fungsi intrinsik newidsebelum optimasi dimulai. Namun demikian, banyak orang menganggap bahwa pengoptimal harus dapat mengidentifikasi bahwa sub-pohon diperluas pada awalnya merupakan objek referensi tunggal dan menyederhanakannya. Ada juga beberapa permintaan Connect untuk memungkinkan terwujudnya CTE atau tabel turunan secara eksplisit.

Implementasi yang lebih umum akan membuat optimizer mempertimbangkan untuk mewujudkan ekspresi umum yang sewenang-wenang untuk meningkatkan kinerja ( CASEdengan subquery adalah contoh lain di mana masalah dapat terjadi hari ini). Microsoft Research menerbitkan sebuah makalah (PDF) pada tahun 2007 itu, meskipun masih belum diimplementasikan hingga saat ini. Untuk saat ini, kami terbatas pada materialisasi eksplisit menggunakan hal-hal seperti variabel tabel dan tabel sementara.

SQLKiwi telah menyebutkan menyusun rencana dalam SSIS, apakah ada cara atau alat yang berguna untuk membantu dalam menyusun rencana yang baik untuk SQL Server?

Ini hanya angan - angan di pihak saya, dan melampaui gagasan memodifikasi panduan rencana. Mungkin, pada prinsipnya, untuk menulis alat untuk memanipulasi XML rencana acara secara langsung, tetapi tanpa instrumentasi pengoptimal tertentu menggunakan alat tersebut kemungkinan akan menjadi pengalaman yang membuat frustasi bagi pengguna (dan pengembang memikirkannya).

Dalam konteks khusus dari pertanyaan ini, alat seperti itu masih tidak dapat mematerialisasikan isi CTE dengan cara yang dapat digunakan oleh banyak konsumen (untuk memberi makan kedua input ke pasangan silang dalam kasus ini). Pengoptimal dan mesin eksekusi mendukung gulungan multi-konsumen, tetapi hanya untuk tujuan tertentu - tidak ada yang dapat dibuat untuk diterapkan pada contoh khusus ini.

Meskipun saya tidak yakin, saya memiliki firasat yang cukup kuat bahwa RelOps dapat diikuti (Nested Loop, Lazy Spool) bahkan jika kueri tidak persis sama dengan paket - misalnya jika Anda menambahkan 4 dan 5 ke CTE , masih terus menggunakan paket yang sama (tampaknya - diuji pada SQL Server 2012 RTM Express).

Ada cukup banyak fleksibilitas di sini. Bentuk luas dari rencana XML digunakan untuk memandu pencarian rencana akhir (meskipun banyak atribut diabaikan sepenuhnya misalnya tipe partisi pada pertukaran) dan aturan pencarian normal juga sangat santai. Sebagai contoh, pemangkasan awal alternatif berdasarkan pertimbangan biaya dinonaktifkan, pengenalan silang secara eksplisit diperbolehkan, dan operasi skalar diabaikan.

Ada terlalu banyak detail untuk dibahas secara mendalam, tetapi penempatan Filter dan Komputasi Skalar tidak dapat dipaksakan, dan predikat formulir column = valuedigeneralisasi sehingga rencana yang berisi X = 1atau X = @Xdapat diterapkan ke kueri yang berisi X = 502atau X = @Y. Fleksibilitas khusus ini dapat sangat membantu dalam menemukan rencana alami untuk dipaksakan.

Dalam contoh spesifik, konstanta All Union dapat selalu diimplementasikan sebagai Scan Constant; jumlah input ke Union All tidak masalah.

Paul White mengatakan GoFundMonica
sumber
3

Tidak ada cara (versi SQL Server hingga 2012) untuk menggunakan kembali gulungan tunggal untuk kedua kejadian CTE. Detail dapat ditemukan dalam jawaban SQLKiwi. Lebih lanjut di bawah ini adalah dua cara untuk mewujudkan CTE dua kali, yang tidak dapat dihindari untuk sifat permintaan. Kedua opsi menghasilkan hitungan panduan jelas 6.

Tautan dari komentar Martin ke situs Quassnoi di sebuah blog tentang rencana memandu CTE adalah inspirasi parsial untuk pertanyaan ini. Ini menjelaskan cara untuk mewujudkan CTE untuk tujuan subquery berkorelasi, yang dirujuk hanya sekali meskipun korelasi dapat menyebabkannya dievaluasi beberapa kali. Itu tidak berlaku untuk kueri dalam pertanyaan.

Opsi 1 - Panduan Perencanaan

Mengambil petunjuk dari jawaban SQLKiwi, saya telah mengurangi panduan ke minimum yang masih akan melakukan pekerjaan, misalnya ConstantScannode hanya daftar 2 operator skalar yang cukup dapat memperluas ke nomor berapa pun.

;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>'
);

Opsi 2 - Pemindaian Jauh

Dengan meningkatkan biaya permintaan dan memperkenalkan Pemindaian Jarak Jauh, hasilnya terwujud.

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;
孔夫子
sumber
2

Dalam keseriusan Anda tidak dapat memotong rencana eksekusi xml dari awal. Membuat mereka menggunakan SSIS adalah fiksi ilmiah. Ya itu semua XML, tetapi mereka berasal dari alam semesta yang berbeda. Melihat blog Paul tentang topik itu , dia mengatakan "banyak cara SSIS memungkinkan ..." jadi mungkin Anda salah paham? Saya tidak berpikir dia mengatakan "menggunakan SSIS untuk membuat rencana" tetapi "tidak akan bagus untuk dapat membuat rencana menggunakan antarmuka drag and drop seperti SSIS". Mungkin, untuk permintaan yang sangat sederhana, Anda bisa mengatur ini, tapi ini sulit, bahkan mungkin buang-buang waktu. Pekerjaan yang sibuk bisa Anda katakan.

Jika saya membuat rencana untuk petunjuk USE PLAN atau panduan rencana, saya punya beberapa pendekatan. Misalnya, saya dapat menghapus catatan dari tabel (misalnya pada salinan db) untuk memengaruhi statistik dan mendorong pengoptimal untuk membuat keputusan yang berbeda. Saya juga menggunakan variabel tabel sebagai ganti semua tabel dalam kueri sehingga pengoptimal berpikir setiap tabel berisi 1 catatan. Kemudian dalam paket yang dibuat, ganti semua variabel tabel dengan nama tabel asli dan tukar sebagai paket. Opsi lain adalah menggunakan opsi WITH STATS_STREAM dari UPDATE STATISTICS untuk memalsukan statistik yang merupakan metode yang digunakan ketika mengkloning salinan hanya-statistik dari basis data misalnya

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

Saya telah menghabiskan beberapa waktu bermain-main dengan rencana eksekusi xml di masa lalu dan saya telah menemukan bahwa pada akhirnya, SQL hanya berjalan "Saya tidak menggunakan itu" dan menjalankan kueri seperti yang diinginkan.

Untuk contoh spesifik Anda, saya yakin Anda sadar bahwa Anda dapat menggunakan set rowcount 3 atau TOP 3 dalam kueri untuk mendapatkan hasil itu, tapi saya rasa itu bukan poin Anda. The benar jawabannya benar-benar akan menjadi: menggunakan tabel temp. Saya akan menjawab bahwa:) Bukan jawaban yang benar akan "menghabiskan berjam-jam bahkan berhari-hari memotong rencana eksekusi XML kustom Anda sendiri di mana Anda mencoba untuk menipu pengoptimal untuk melakukan spool malas untuk CTE yang bahkan mungkin tidak bekerja, akan terlihat pintar tetapi juga tidak mungkin untuk mempertahankan ".

Tidak mencoba menjadi tidak konstruktif di sana, hanya pendapat saya - harapan yang membantu.

wBob
sumber
Serius, Paket XML diabaikan?!, Saya pikir itu intinya? Jika tidak valid itu harus dibuang.
crokusek
Saya merujuk ke acara Panduan Gagal acara.
wBob
2

Apakah ada cara APA SAJA ...

Akhirnya di SQL 2016 CTP 3.0 ada cara, jenis:)

Dengan menggunakan tanda jejak dan Acara yang Diperpanjang sebagaimana dirinci oleh Dmitry Pilugin di sini , Anda dapat (agak sewenang-wenang) memancing tiga panduan unik dari tahap-tahap menengah dari eksekusi permintaan.

NB Kode ini TIDAK dimaksudkan untuk produksi atau penggunaan serius sehubungan dengan pemaksaan rencana CTE, hanya pandangan ringan pada bendera jejak baru dan cara berbeda dalam melakukan sesuatu:

-- 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

Diuji pada versi (CTP3.2) - 13.0.900.73 (x64), hanya untuk bersenang-senang.

wBob
sumber
1

Saya menemukan traceflag 8649 (force parallel plan) yang menginduksi perilaku ini untuk kolom panduan kiri pada contoh 2008, R2 dan 2012 saya. Saya tidak perlu menggunakan flag pada SQL 2005 di mana CTE berperilaku dengan benar. Saya mencoba menggunakan paket yang dihasilkan di SQL 2005 dalam contoh yang lebih tinggi tetapi tidak divalidasi.

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 )

Baik menggunakan petunjuk, menggunakan panduan rencana termasuk petunjuk atau menggunakan rencana yang dihasilkan oleh permintaan dengan petunjuk di dalam USE PLAN dll semua bekerja. cte newid

wBob
sumber
Terima kasih sudah mencoba lagi. Kueri tidak terlihat berbeda dengan atau tanpa tanda jejak pada 2008/2012. Tidak begitu yakin apakah ini contoh SQL Server saya atau apa yang Anda coba tampilkan. Saya masih melihat 18 panduan. Apa yang kamu lihat?
孔夫子
3 panduan berbeda di sisi kiri (kolom panduan), masing-masing diulang tiga kali. 9 panduan unik di sisi kanan (kolom guidb), jadi setidaknya bit kiri berperilaku seperti yang Anda inginkan lol. Saya telah menambahkan gambar ke jawaban lain untuk semoga sedikit memperjelas. Langkah kecil. Saya juga harus mencatat dalam SQL 2005, saya mendapatkan 6 panduan unik, 3 di sebelah kiri, 3 di sebelah kanan.
wBob
Juga perhatikan bahwa menghapus 'semua' juga mendapatkan 6 guids unik, masing-masing 3 sisi.
wBob
Dapat membuat traceflag tidak berfungsi dengan memiliki server maxdop 1.
wBob