Saya punya pertanyaan yang berjalan dalam jumlah waktu yang dapat diterima tetapi saya ingin memeras kinerja sebanyak mungkin dari itu.
Operasi yang saya coba tingkatkan adalah "Index Seek" di sebelah kanan paket, dari Node 17.
Saya telah menambahkan indeks yang sesuai tetapi perkiraan yang saya dapatkan untuk operasi itu adalah setengah dari yang seharusnya.
Saya telah mencari untuk mengubah indeks saya dan menambahkan tabel sementara dan menulis ulang kueri, tetapi saya tidak bisa menyederhanakannya lebih dari ini untuk mendapatkan perkiraan yang tepat.
Adakah yang punya saran tentang apa lagi yang bisa saya coba?
Rencana lengkap dan detailnya dapat ditemukan di sini .
Paket tanpa anonim dapat ditemukan di sini.
Memperbarui:
Saya merasa versi awal dari pertanyaan ini menimbulkan banyak kebingungan, jadi saya akan menambahkan kode asli dengan beberapa penjelasan.
create procedure [dbo].[someProcedure] @asType int, @customAttrValIds idlist readonly
as
begin
set nocount on;
declare @dist_ca_id int;
select *
into #temp
from @customAttrValIds
where id is not null;
select @dist_ca_id = count(distinct CustomAttrID)
from CustomAttributeValues c
inner join #temp a on c.Id = a.id;
select a.Id
, a.AssortmentId
from Assortments a
inner join AssortmentCustomAttributeValues acav
on a.Id = acav.Assortment_Id
inner join CustomAttributeValues cav
on cav.Id = acav.CustomAttributeValue_Id
where a.AssortmentType = @asType
and acav.CustomAttributeValue_Id in (select id from #temp)
group by a.AssortmentId
, a.Id
having count(distinct cav.CustomAttrID) = @dist_ca_id
option(recompile);
end
Jawaban:
Mengapa penamaan awal yang aneh di tautan pasteThePlan?
Jawaban : Karena saya menggunakan rencana anonim dari SQL Sentry Plan Explorer.
Mengapa
OPTION RECOMPILE
?Jawab : Karena saya mampu melakukan kompilasi ulang untuk menghindari sniffing parameter (data / bisa miring). Saya telah menguji dan saya senang dengan rencana yang dihasilkan Pengoptimal saat menggunakan
OPTION RECOMPILE
.WITH SCHEMABINDING
?Jawab : Saya benar-benar ingin menghindarinya dan hanya akan menggunakannya ketika saya memiliki tampilan yang diindeks Bagaimanapun, ini adalah fungsi sistem (
COUNT()
) jadi tidak ada gunanya diSCHEMABINDING
sini.
Jawaban untuk pertanyaan yang lebih mungkin:
Kenapa saya menggunakan
INSERT INTO #temp FROM @customAttrributeValues
?Jawaban : Karena saya perhatikan dan sekarang tahu bahwa ketika menggunakan variabel yang dicolokkan ke kueri, setiap perkiraan yang keluar dari bekerja dengan variabel selalu 1. Dan saya menguji menempatkan data ke tabel temp dan Estimasi kemudian sama dengan Baris Aktual .
Kenapa saya menggunakan
and acav.CustomAttributeValue_Id in (select id from #temp)
?Jawab : Saya bisa menggantinya dengan BERGABUNG di #temp, tetapi pengembang sangat bingung dan menawarkan
IN
opsi. Saya tidak benar-benar berpikir akan ada perbedaan bahkan dengan mengganti dan bagaimanapun, tidak ada masalah dengan ini.
sumber
#temp
kreasi dan penggunaan akan menjadi masalah untuk kinerja, bukan keuntungan. Anda menyimpan ke tabel yang tidak diindeks hanya untuk digunakan satu kali. Coba hapus sepenuhnya (dan mungkin mengubahnyain (select id from #temp)
menjadiexists
subquery.select id from @customAttrValIds
bukannyaselect id from #temp
dan jumlah baris diperkirakan1
untuk variabel dan3
untuk #temp (yang cocok dengan # baris sebenarnya). Itulah mengapa saya diganti@
dengan#
. Dan saya DO ingat bicara (dari Brent O atau Aaron Bertrand) di mana mereka mengatakan bahwa ketika menggunakan variabel tbl perkiraan untuk itu akan selalu 1. dan sebagai perbaikan untuk mendapatkan perkiraan yang lebih baik mereka akan menggunakan tabel sementara.Jawaban:
Paket ini dikompilasi pada contoh SQL Server 2008 R2 RTM (build 10.50.1600). Anda harus menginstal Paket Layanan 3 (build 10.50.6000), diikuti oleh tambalan terbaru untuk membawanya ke build terbaru 10.50.6542. Ini penting karena sejumlah alasan, termasuk keamanan, perbaikan bug, dan fitur baru.
Parameter Optimalisasi Penempatan
Relevan dengan pertanyaan ini, SQL Server 2008 R2 RTM tidak mendukung Parameter Embedding Optimization (PEO) untuk
OPTION (RECOMPILE)
. Saat ini, Anda membayar biaya kompilasi ulang tanpa menyadari salah satu manfaat utama.Ketika PEO tersedia, SQL Server dapat menggunakan nilai literal yang disimpan dalam variabel dan parameter lokal langsung di paket kueri. Ini dapat menyebabkan penyederhanaan dramatis dan peningkatan kinerja. Ada informasi lebih lanjut tentang itu di artikel saya, Parameter Sniffing, Embedding, dan Opsi RECOMPILE .
Hash, Sortir, dan Tukar Tumpahan
Ini hanya ditampilkan dalam rencana eksekusi ketika kueri dikompilasi pada SQL Server 2012 atau yang lebih baru. Dalam versi sebelumnya, kami harus memantau tumpahan saat kueri mengeksekusi menggunakan Profiler atau Extended Events. Tumpahan selalu mengakibatkan I / O fisik menjadi (dan dari) tempdb dukungan penyimpanan persisten , yang dapat memiliki konsekuensi kinerja yang penting, terutama jika tumpahan besar, atau jalur I / O berada di bawah tekanan.
Dalam rencana eksekusi Anda, ada dua operator Pencocokan Hash (Agregat). Memori yang dicadangkan untuk tabel hash didasarkan pada estimasi untuk baris output (dengan kata lain, ini sebanding dengan jumlah grup yang ditemukan saat runtime). Memori yang diberikan diperbaiki tepat sebelum eksekusi dimulai, dan tidak dapat tumbuh selama eksekusi, terlepas dari berapa banyak memori bebas yang dimiliki instance. Dalam paket yang disediakan, kedua operator Pencocokan Hash (Agregat) menghasilkan lebih banyak baris dari yang diharapkan oleh pengoptimal, dan karenanya mungkin mengalami tumpahan ke tempdb saat runtime.
Ada juga operator Hash Match (Inner Join) dalam paket tersebut. Memori yang dicadangkan untuk tabel hash didasarkan pada estimasi untuk baris input sisi probe . Input input memperkirakan 847.399 baris, tetapi 1.223.636 ditemui pada saat dijalankan. Kelebihan ini juga dapat menyebabkan tumpahan hash.
Agregat Redundan
Pencocokan Hash (Agregat) pada node 8 melakukan operasi pengelompokan aktif
(Assortment_Id, CustomAttrID)
, tetapi baris input sama dengan baris output:Ini menunjukkan bahwa kombinasi kolom adalah kunci (sehingga pengelompokan secara semantik tidak diperlukan). Biaya melakukan agregat redundan dinaikkan oleh kebutuhan untuk melewati 1,4 juta baris dua kali di seluruh pertukaran partisi hash (operator Paralelisme di kedua sisi).
Mengingat bahwa kolom yang terlibat berasal dari tabel yang berbeda, lebih sulit daripada biasanya untuk mengomunikasikan informasi keunikan ini kepada pengoptimal, sehingga dapat menghindari operasi pengelompokan yang berlebihan dan pertukaran yang tidak perlu.
Distribusi benang tidak efisien
Seperti dicatat dalam jawaban Joe Obbish , pertukaran di simpul 14 menggunakan partisi hash untuk mendistribusikan baris di antara utas. Sayangnya, sejumlah kecil baris dan penjadwal yang tersedia berarti ketiga baris berakhir pada satu utas. Rencana yang tampaknya paralel berjalan secara serial (dengan overhead paralel) sejauh pertukaran pada simpul 9.
Anda bisa mengatasinya (untuk mendapatkan round-robin atau mempartisi partisi) dengan menghilangkan Sorting Distinct pada node 13. Cara termudah untuk melakukannya adalah dengan membuat kunci primer yang dikelompokkan di atas
#temp
meja, dan melakukan operasi yang berbeda saat memuat tabel:Caching statistik tabel sementara
Meskipun menggunakan
OPTION (RECOMPILE)
, SQL Server masih dapat men-cache objek tabel sementara dan statistik terkait antara panggilan prosedur. Ini umumnya merupakan optimasi kinerja yang disambut baik, tetapi jika tabel sementara diisi dengan jumlah data yang sama pada panggilan prosedur yang berdekatan, rencana yang dikompilasi ulang mungkin didasarkan pada statistik yang salah (di-cache dari eksekusi sebelumnya). Ini dijelaskan dalam artikel saya, Tabel Sementara dalam Prosedur yang Disimpan dan Penjelasan Tabel Sementara Caching Dijelaskan .Untuk menghindari ini, gunakan
OPTION (RECOMPILE)
bersama-sama dengan eksplisitUPDATE STATISTICS #TempTable
setelah tabel sementara diisi, dan sebelum direferensikan dalam kueri.Penulisan ulang kueri
Bagian ini mengasumsikan bahwa perubahan pada pembuatan
#Temp
tabel telah dilakukan.Mengingat biaya dari kemungkinan tumpahan hash dan agregat berlebihan (dan pertukaran sekitarnya), mungkin membayar untuk mewujudkan set di node 10:
Itu
PRIMARY KEY
ditambahkan dalam langkah terpisah untuk memastikan membangun indeks memiliki informasi kardinalitas yang akurat, dan untuk menghindari masalah caching statistik tabel sementara.Materialisasi ini sangat mungkin terjadi dalam memori (menghindari tempdb I / O) jika instance memiliki cukup memori. Ini bahkan lebih mungkin setelah Anda memutakhirkan ke SQL Server 2012 (SP1 CU10 / SP2 CU1 atau lebih baru), yang telah meningkatkan perilaku Eager Write .
Tindakan ini memberikan informasi kardinalitas akurat pengoptimal pada set perantara, memungkinkannya untuk membuat statistik, dan memungkinkan kami untuk mendeklarasikan
(Assortment_Id, CustomAttrID)
sebagai kunci.Rencana untuk populasi
#Temp2
seharusnya terlihat seperti ini (perhatikan pemindaian indeks berkerumun#Temp
, tidak ada Sorting Berbeda, dan pertukaran sekarang menggunakan partisi round-robin row):Dengan set yang tersedia, kueri terakhir menjadi:
Kami dapat menulis ulang secara manual
COUNT_BIG(DISTINCT...
sebagai yang sederhanaCOUNT_BIG(*)
, tetapi dengan informasi kunci yang baru, pengoptimal melakukannya untuk kami:Rencana akhir dapat menggunakan loop / hash / gabungan bergabung tergantung pada informasi statistik tentang data yang saya tidak memiliki akses. Satu catatan kecil lainnya: Saya berasumsi bahwa indeks suka
CREATE [UNIQUE?] NONCLUSTERED INDEX IX_ ON dbo.Assortments (AssortmentType, Id, AssortmentId);
ada.Bagaimanapun, hal penting tentang rencana akhir adalah bahwa perkiraan harus jauh lebih baik, dan urutan kompleks dari operasi pengelompokan telah direduksi menjadi Agregat Stream tunggal (yang tidak memerlukan memori dan karenanya tidak dapat tumpah ke disk).
Sulit untuk mengatakan bahwa kinerja sebenarnya akan lebih baik dalam hal ini dengan tabel sementara tambahan, tetapi perkiraan dan pilihan rencana akan jauh lebih tangguh terhadap perubahan volume data dan distribusi dari waktu ke waktu. Itu mungkin lebih berharga dalam jangka panjang daripada peningkatan kinerja kecil hari ini. Bagaimanapun, Anda sekarang memiliki lebih banyak informasi yang menjadi dasar keputusan akhir Anda.
sumber
Perkiraan kardinalitas pada permintaan Anda sebenarnya sangat bagus. Sangat jarang untuk mendapatkan jumlah baris yang diperkirakan untuk mencocokkan jumlah baris yang sebenarnya persis, terutama ketika Anda memiliki banyak ini bergabung. Bergabunglah dengan perkiraan kardinalitas rumit untuk dilakukan optimizer. Satu hal penting yang perlu diperhatikan adalah bahwa jumlah baris yang diperkirakan untuk bagian dalam loop bersarang adalah per eksekusi dari loop itu. Jadi ketika SQL Server mengatakan bahwa 463869 baris akan diambil dengan indeks mencari estimasi nyata dalam kasus ini adalah jumlah eksekusi (2) * 463869 = 927738 yang tidak jauh dari jumlah baris aktual, 1391608. Anehnya, jumlah baris yang diperkirakan mendekati sempurna segera setelah loop bersarang bergabung pada simpul ID 10.
Perkiraan kardinalitas yang buruk sebagian besar merupakan masalah ketika pengoptimal kueri memilih rencana yang salah atau tidak memberikan cukup memori untuk rencana tersebut. Saya tidak melihat tumpahan ke tempdb untuk paket ini, jadi ingatan terlihat baik-baik saja. Untuk bergabung dengan loop bersarang yang Anda panggil, Anda memiliki tabel luar kecil dan tabel dalam diindeks. Apa yang salah dengan itu? Lebih tepatnya, apa yang Anda harapkan dari pengoptimal query lakukan secara berbeda di sini?
Dalam hal meningkatkan kinerja, hal yang menonjol bagi saya adalah bahwa SQL Server menggunakan algoritma hashing untuk mendistribusikan baris paralel yang menghasilkan semuanya berada di utas yang sama:
Akibatnya, satu utas melakukan semua pekerjaan dengan indeks mencari:
Itu berarti bahwa kueri Anda secara efektif tidak berjalan secara paralel sampai partisi ulang mengalirkan operator pada simpul id 9. Apa yang Anda mungkin inginkan adalah partisi round robin sehingga setiap baris berakhir pada utasnya sendiri. Itu akan memungkinkan dua utas untuk melakukan pencarian indeks untuk simpul id 17. Menambahkan
TOP
operator yang berlebihan mungkin membuat Anda membuat partisi robin bulat. Saya dapat menambahkan detail di sini jika Anda mau.Jika Anda benar-benar ingin fokus pada perkiraan kardinalitas, Anda bisa meletakkan baris setelah bergabung pertama ke tabel temp. Jika Anda mengumpulkan statistik di tabel temp yang memberikan optimizer lebih banyak informasi tentang tabel terluar untuk loop bersarang bergabung yang Anda panggil. Itu juga bisa menghasilkan partisi round robin.
Jika Anda tidak menggunakan tanda jejak 4199 atau 2301, Anda dapat mempertimbangkannya. Bendera jejak 4199 menawarkan berbagai perbaikan optimizer, tetapi mereka dapat menurunkan beberapa beban kerja. Trace flag 2301 mengubah beberapa asumsi kardinalitas gabungan dari optimizer kueri dan membuatnya bekerja lebih keras. Dalam kedua kasus, uji dengan cermat sebelum mengaktifkannya.
sumber
Saya percaya mendapatkan perkiraan yang lebih baik tentang bergabung itu tidak akan mengubah rencana, kecuali 1,4 mill adalah bagian yang cukup dari tabel untuk membuat pengoptimal memilih scan indeks (bukan cluster) dengan hash atau gabungan gabung. Saya menduga itu tidak akan menjadi masalah di sini, juga tidak benar-benar bermanfaat, tetapi Anda dapat menguji efeknya dengan mengganti gabungan internal terhadap CustomAttributeValues dengan gabungan hash dalam dan gabungan gabungan internal .
Saya juga telah melihat kode secara lebih luas dan tidak dapat melihat cara untuk memperbaikinya - saya tentu saja tertarik untuk terbukti salah. Dan jika Anda ingin memposting logika lengkap tentang apa yang ingin Anda capai, saya akan tertarik pada tampilan lain.
sumber
OPTION(FORCE ORDER)
, yang mencegah pengoptimalan pemesanan ulang bergabung dari urutan teks, dan banyak optimasi lainnya selain itu.Anda tidak akan membaik dari Pencarian Indeks [non-clustered]. Satu-satunya hal yang lebih baik daripada pencarian indeks non-cluster adalah Pencarian Indeks Clustered.
Juga, saya telah menjadi DBA SQL selama sepuluh tahun terakhir, dan pengembang SQL selama lima tahun sebelumnya, dan dalam pengalaman saya, sangat jarang menemukan peningkatan pada SQL Query dengan mempelajari rencana eksekusi yang tidak dapat Anda lakukan. t menemukan dengan cara lain. Alasan utama untuk menghasilkan rencana pelaksanaan adalah karena sering kali akan menyarankan indeks yang hilang kepada Anda yang dapat Anda tambahkan untuk meningkatkan kinerja.
Keuntungan kinerja utama akan menyesuaikan SQL Query itu sendiri, jika ada inefisiensi di sana. Sebagai contoh, beberapa bulan yang lalu saya mendapatkan fungsi SQL untuk menjalankan 160 kali lebih cepat dengan menulis ulang
SELECT UNION SELECT
tabel pivot gaya untuk menggunakanPIVOT
operator SQL standar .Jadi mari kita lihat,
SELECT * INTO
umumnya kurang efisien daripada standarINSERT Object1 (column list) SELECT column list
. Jadi saya akan menulis ulang itu. Selanjutnya, jika Function1 didefinisikan tanpa aWITH SCHEMABINDING
, menambahkanWITH SCHEMABINDING
klausa akan membuatnya berjalan lebih cepat.Anda telah memilih banyak alias yang tidak masuk akal, seperti alias Object2 sebagai Object3. Anda harus memilih alias yang lebih baik yang tidak mengaburkan kode. Anda memiliki "Object7.Column5 di (pilih Column1 dari Object1)".
IN
klausul seperti ini selalu lebih efisien ditulis sebagaiEXISTS (SELECT 1 FROM Object1 o1 WHERE o1.Column1 = Object7.Column5)
. Mungkin saya seharusnya menulis itu sebaliknya.EXISTS
akan selalu setidaknya sebaikIN
. Ini tidak selalu lebih baik, tetapi biasanya memang demikian.Juga, saya meragukannya
option(recompile)
meningkatkan kinerja permintaan di sini. Saya akan menguji menghapusnya.sumber