Cara mengoptimalkan kueri T-SQL menggunakan Rencana Eksekusi

15

Saya memiliki query SQL yang telah saya habiskan selama dua hari terakhir untuk mencoba mengoptimalkan menggunakan trial-and-error dan rencana eksekusi, tetapi tidak berhasil. Maafkan saya karena melakukan ini, tetapi saya akan memposting seluruh rencana eksekusi di sini. Saya telah melakukan upaya untuk membuat nama tabel dan kolom dalam permintaan dan rencana eksekusi generik baik untuk singkatnya maupun untuk melindungi IP perusahaan saya. Rencana eksekusi dapat dibuka dengan SQL Sentry Plan Explorer .

Saya telah melakukan cukup banyak T-SQL, tetapi menggunakan rencana eksekusi untuk mengoptimalkan permintaan saya adalah area baru bagi saya dan saya benar-benar mencoba memahami bagaimana melakukannya. Jadi, jika ada yang bisa membantu saya dengan ini dan menjelaskan bagaimana rencana eksekusi ini dapat diuraikan untuk menemukan cara dalam kueri untuk mengoptimalkannya, saya akan berterima kasih selamanya. Saya memiliki banyak pertanyaan lagi untuk dioptimalkan - Saya hanya perlu batu loncatan untuk membantu saya dengan yang pertama ini.

Ini pertanyaannya:

DECLARE @Param0 DATETIME     = '2013-07-29';
DECLARE @Param1 INT          = CONVERT(INT, CONVERT(VARCHAR, @Param0, 112))
DECLARE @Param2 VARCHAR(50)  = 'ABC';
DECLARE @Param3 VARCHAR(100) = 'DEF';
DECLARE @Param4 VARCHAR(50)  = 'XYZ';
DECLARE @Param5 VARCHAR(100) = NULL;
DECLARE @Param6 VARCHAR(50)  = 'Text3';

SET NOCOUNT ON

DECLARE @MyTableVar TABLE
(
    B_Var1_PK int,
    Job_Var1 varchar(512),
    Job_Var2 varchar(50)
)

INSERT INTO @MyTableVar (B_Var1_PK, Job_Var1, Job_Var2) 
SELECT B_Var1_PK, Job_Var1, Job_Var2 FROM [fn_GetJobs] (@Param1, @Param2, @Param3, @Param4, @Param6);

CREATE TABLE #TempTable
(
    TTVar1_PK INT PRIMARY KEY,
    TTVar2_LK VARCHAR(100),
    TTVar3_LK VARCHAR(50),
    TTVar4_LK INT,
    TTVar5 VARCHAR(20)
);

INSERT INTO #TempTable
SELECT DISTINCT
    T.T1_PK,
    T.T1_Var1_LK,
    T.T1_Var2_LK,
    MAX(T.T1_Var3_LK),
    T.T1_Var4_LK
FROM
    MyTable1 T
    INNER JOIN feeds.MyTable2 A ON A.T2_Var1 = T.T1_Var4_LK
    INNER JOIN @MyTableVar B ON B.Job_Var2 = A.T2_Var2 AND B.Job_Var1 = A.T2_Var3
GROUP BY T.T1_PK, T.T1_Var1_LK, T.T1_Var2_LK, T.T1_Var4_LK

-- This is the slow statement...
SELECT 
    CASE E.E_Var1_LK
        WHEN 'Text1' THEN T.TTVar2_LK + '_' + F.F_Var1
        WHEN 'Text2' THEN T.TTVar2_LK + '_' + F.F_Var2
        WHEN 'Text3' THEN T.TTVar2_LK
    END,
    T.TTVar4_LK,
    T.TTVar3_LK,
    CASE E.E_Var1_LK
        WHEN 'Text1' THEN F.F_Var1
        WHEN 'Text2' THEN F.F_Var2
        WHEN 'Text3' THEN T.TTVar5
    END,
    A.A_Var3_FK_LK,
    C.C_Var1_PK,
    SUM(CONVERT(DECIMAL(18,4), A.A_Var1) + CONVERT(DECIMAL(18,4), A.A_Var2))
FROM #TempTable T
    INNER JOIN TableA (NOLOCK) A ON A.A_Var4_FK_LK  = T.TTVar1_PK
    INNER JOIN @MyTableVar     B ON B.B_Var1_PK     = A.Job
    INNER JOIN TableC (NOLOCK) C ON C.C_Var2_PK     = A.A_Var5_FK_LK
    INNER JOIN TableD (NOLOCK) D ON D.D_Var1_PK     = A.A_Var6_FK_LK
    INNER JOIN TableE (NOLOCK) E ON E.E_Var1_PK     = A.A_Var7_FK_LK  
    LEFT OUTER JOIN feeds.TableF (NOLOCK) F ON F.F_Var1 = T.TTVar5
WHERE A.A_Var8_FK_LK = @Param1
GROUP BY
    CASE E.E_Var1_LK
        WHEN 'Text1' THEN T.TTVar2_LK + '_' + F.F_Var1
        WHEN 'Text2' THEN T.TTVar2_LK + '_' + F.F_Var2
        WHEN 'Text3' THEN T.TTVar2_LK
    END,
    T.TTVar4_LK,
    T.TTVar3_LK,
    CASE E.E_Var1_LK 
        WHEN 'Text1' THEN F.F_Var1
        WHEN 'Text2' THEN F.F_Var2
        WHEN 'Text3' THEN T.TTVar5
    END,
    A.A_Var3_FK_LK, 
    C.C_Var1_PK


IF OBJECT_ID(N'tempdb..#TempTable') IS NOT NULL
BEGIN
    DROP TABLE #TempTable
END
IF OBJECT_ID(N'tempdb..#TempTable') IS NOT NULL
BEGIN
    DROP TABLE #TempTable
END

Apa yang saya temukan adalah bahwa pernyataan ketiga (dikomentari lambat) adalah bagian yang paling memakan waktu. Dua pernyataan sebelum kembali hampir secara instan.

Paket eksekusi tersedia sebagai XML di tautan ini .

Lebih baik klik kanan dan simpan lalu buka di SQL Sentry Plan Explorer atau beberapa perangkat lunak lain daripada membuka di browser Anda.

Jika Anda memerlukan informasi lebih lanjut dari saya tentang tabel atau data, jangan ragu untuk bertanya.

Neo
sumber
2
Statistik Anda jauh dari itu. Kapan terakhir kali Anda menghapus indeks yang terfragmentasi atau diperbarui? Juga, saya akan mencoba menggunakan tabel temp, alih-alih variabel tabel, @MyTableVar, karena pengoptimal benar-benar tidak dapat menggunakan statistik pada variabel tabel.
Adam Haines
Terima kasih atas balasan Anda, Adam. Mengubah @MyTableVar ke tabel temp tidak memiliki efek apa pun, tetapi hanya sejumlah kecil baris (yang dapat dilihat dari rencana eksekusi). Apa yang ada dalam rencana eksekusi yang menunjukkan bahwa statistik saya jauh? Apakah ini menunjukkan indeks mana yang harus direorganisasi atau dibangun kembali, dan tabel mana yang harus diperbarui statistiknya?
Neo
3
Gabung hash di kanan bawah memiliki sekitar 24.000 baris dalam input build tetapi sebenarnya 3.285.620 jadi mungkin tumpah ke tempdb. yaitu estimasi untuk baris yang dihasilkan dari gabungan antara TableAdan @MyTableVarjauh. Juga jumlah baris yang masuk ke jenis jauh lebih besar dari yang diperkirakan sehingga mereka bisa tumpah juga.
Martin Smith

Jawaban:

21

Sebelum sampai ke jawaban utama, ada dua perangkat lunak yang perlu Anda perbarui.

Pembaruan Perangkat Lunak yang Diperlukan

Yang pertama adalah SQL Server. Anda menjalankan SQL Server 2008 Paket Layanan 1 (build 2531). Anda harus ditambal hingga setidaknya Paket Layanan saat ini (SQL Server 2008 Paket Layanan 3 - build 5500). Membangun SQL Server 2008 terbaru pada saat penulisan adalah Paket Layanan 3, Pembaruan Kumulatif 12 (membangun 5844).

Bagian kedua dari perangkat lunak adalah SQL Sentry Plan Explorer . Versi terbaru memiliki fitur dan perbaikan baru yang signifikan, termasuk kemampuan untuk langsung mengunggah rencana permintaan untuk analisis ahli (tidak perlu menempelkan XML di mana pun!)

Analisis Rencana Kueri

Perkiraan kardinalitas untuk variabel tabel benar, berkat kompilasi tingkat pernyataan:

estimasi variabel tabel

Sayangnya, variabel tabel tidak mempertahankan statistik distribusi, sehingga yang diketahui oleh pengoptimal adalah bahwa ada enam baris; ia tidak tahu apa-apa tentang nilai yang mungkin ada dalam enam baris itu. Informasi ini sangat penting mengingat bahwa operasi selanjutnya adalah bergabung ke tabel lain. Perkiraan kardinalitas dari gabungan itu didasarkan pada tebakan liar oleh pengoptimal:

estimasi bergabung pertama

Sejak saat itu, rencana yang dipilih oleh pengoptimal didasarkan pada informasi yang salah, sehingga tidak mengherankan jika kinerjanya sangat buruk. Secara khusus, memori yang disisihkan untuk jenis dan tabel hash untuk bergabung hash akan terlalu kecil. Pada waktu eksekusi, jenis meluap dan operasi hashing akan tumpah ke disk tempdb fisik .

SQL Server 2008 tidak menyoroti ini dalam rencana eksekusi; Anda dapat memonitor tumpahan menggunakan Extended Events atau Profiler Sort Warnings dan Hash Warnings . Memori dicadangkan untuk jenis dan hash berdasarkan perkiraan kardinalitas sebelum eksekusi dimulai, dan tidak dapat ditingkatkan selama eksekusi terlepas dari berapa banyak memori cadangan yang mungkin dimiliki SQL Server Anda. Perkiraan jumlah baris yang akurat sangat penting untuk setiap rencana eksekusi yang melibatkan operasi yang memakan memori ruang kerja.

Permintaan Anda juga parameter. Anda harus mempertimbangkan OPTION (RECOMPILE)untuk menambah kueri jika nilai parameter yang berbeda memengaruhi rencana kueri. Anda mungkin harus mempertimbangkan untuk menggunakannya, sehingga pengoptimal dapat melihat nilai @Param1pada waktu kompilasi. Jika tidak ada yang lain, ini dapat membantu pengoptimal menghasilkan perkiraan yang lebih masuk akal untuk pencarian indeks yang ditunjukkan di atas, mengingat bahwa tabel sangat besar, dan dipartisi. Ini juga dapat mengaktifkan penghapusan partisi statis.

Coba kueri lagi dengan tabel sementara, bukan variabel tabel dan OPTION (RECOMPILE) . Anda juga harus mencoba mematerialisasikan hasil gabungan pertama ke tabel sementara lainnya, dan menjalankan kueri lainnya terhadap itu. Jumlah baris tidak terlalu besar (3.285.620) sehingga ini harus cukup cepat. Pengoptimal kemudian akan memiliki perkiraan kardinalitas dan statistik distribusi yang tepat untuk hasil bergabung. Dengan keberuntungan, sisa rencana akan jatuh ke tempatnya dengan baik.

Bekerja dari properti yang ditunjukkan dalam paket, kueri materialisasi adalah:

SELECT
    A.A_Var7_FK_LK,
    A.A_Var4_FK_LK,
    A.A_Var6_FK_LK, 
    A.A_Var5_FK_LK,
    A.A_Var1,
    A.A_Var2,
    A.A_Var3_FK_LK
INTO #AnotherTempTable
FROM @MyTableVar AS B
JOIN TableA AS A
    ON A.Job = B.B_Var1_PK
WHERE
    A_Var8_FK_LK = @Param1;

Anda juga bisa INSERTke tabel sementara yang telah ditentukan (tipe data yang benar tidak ditampilkan dalam paket, jadi saya tidak bisa melakukan bagian itu). Tabel sementara baru mungkin atau mungkin tidak mendapat manfaat dari indeks berkerumun dan tidak tercakup.

Paul White Reinstate Monica
sumber
Terima kasih banyak atas jawaban mendalam ini. Maaf butuh satu minggu untuk membalas - Saya sudah mengerjakan ini setiap hari diselingi dengan pekerjaan lain. Saya telah mengimplementasikan saran Anda untuk mewujudkan join ke TableA #AnotherTempTable. Ini tampaknya memiliki dampak terbaik - saran lain (menggunakan tabel temp bukan variabel tabel untuk @MyTableVar, dan menggunakan OPTION (RECOMPILE)tidak memiliki banyak efek atau sama sekali. 'Anonimisasi' dan 'Posting ke SQLPerformance.com' opsi di SQL Sentry Plan Explorer sangat bagus - Saya baru saja menggunakannya: answer.sqlperformance.com/questions/1087
Neo
-6

Saya perhatikan harus ada PK di @MyTableVar dan setuju bahwa #MyTableVar sering berkinerja lebih baik (terutama dengan jumlah baris yang lebih besar).

Kondisi dalam klausa mana

   WHERE A.A_Var8_FK_LK = @Param1

harus dipindahkan ke bagian dalam bergabung dengan AND. Pengoptimal tidak cukup pintar dalam pengalaman saya untuk melakukan ini (maaf tidak melihat rencana) dan itu bisa membuat perbedaan besar.

Jika perubahan-perubahan itu tidak menunjukkan perbaikan, saya selanjutnya akan membuat tabel temp lain dari A dan semua hal yang digabungkan menjadi dibatasi (baik?) Oleh A.A_Var8_FK_LK = @ Param1 jika pengelompokan itu masuk akal bagi Anda.

Kemudian buat indeks berkerumun di tabel temp (baik sebelum atau setelah pembuatan) untuk kondisi bergabung berikutnya.

Kemudian gabungkan hasil itu ke beberapa tabel (F dan T) yang tersisa.

Bam, yang membutuhkan rencana permintaan yang menyebalkan ketika estimasi baris tidak aktif dan terkadang tidak mudah diperbaiki ). Saya berasumsi Anda memiliki indeks yang tepat yang pertama saya akan periksa dalam rencana itu.

Jejak dapat menunjukkan tumpahan tempdb yang mungkin atau mungkin tidak berdampak drastis.

Pendekatan alternatif lain - yang lebih cepat untuk mencoba setidaknya - adalah untuk memesan tabel dari jumlah baris terendah (A) ke tertinggi dan kemudian mulai menambahkan gabungan, hash, dan loop ke gabungan. Saat ada petunjuk, pesanan gabungan diperbaiki seperti yang ditentukan. Pengguna lain dengan bijak menghindari pendekatan ini karena dapat membahayakan dalam jangka panjang jika jumlah baris relatif berubah secara dramatis. Sejumlah petunjuk minimum diinginkan.

Jika Anda melakukan banyak dari ini, mungkin pengoptimal komersial layak dicoba (atau dicoba) dan masih merupakan pengalaman belajar yang baik.

crokusek
sumber
Ya itu. Ini memastikan baris yang dikembalikan oleh A dibatasi oleh kendala. Jika tidak, pengoptimal dapat bergabung terlebih dahulu dan menerapkan kendala nanti. Saya berurusan dengan ini setiap hari.
crokusek
4
@crokusek Anda salah. Pengoptimal SQL-Server cukup bagus untuk mengetahui kueri yang setara (apakah kondisi berada di WHERE atau klausa ON) ketika itu adalah INNER bergabung.
ypercubeᵀᴹ
6
Anda mungkin menemukan seri Paul White di Pengoptimal Kueri berguna.
Martin Smith
Ini kebiasaan buruk. Mungkin itu akan untuk kasus khusus ini (di mana ada satu kendala) tapi saya datang dari tanah beberapa pengembang menumpuk pada kondisi DAN di mana klausa. SQL Server tidak tidak konsisten "bergerak" mereka kembali ke bergabung untuk Anda.
crokusek
Setuju salah untuk bagian luar (dan gabungan kanan). Tetapi ketika hanya ada ekspresi DAN'd dalam klausa di mana dan setiap istilah hanya sesuai dengan gabungan batin tertentu, istilah itu dapat dengan aman dan percaya diri dipindahkan ke lokasi "on" sebagai optimasi dan praktik terbaik (imo). Apakah itu kondisi penggabungan yang "benar" atau hanya kendala tetap adalah sekunder untuk kenaikan kinerja yang besar. Tautan itu untuk kasus sepele. Kehidupan nyata memiliki banyak kondisi dimana dengan convert () dan matematika dan semacamnya menjadikan mereka kandidat yang lebih baik untuk mendapatkan praktik terbaik.
crokusek