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:
CREATE TABLE #Temp
(
id integer NOT NULL PRIMARY KEY CLUSTERED
);
INSERT #Temp
(
id
)
SELECT DISTINCT
CAV.id
FROM @customAttrValIds AS CAV
WHERE
CAV.id IS NOT NULL;
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 eksplisit UPDATE 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:
CREATE TABLE #Temp2
(
CustomAttrID integer NOT NULL,
Assortment_Id integer NOT NULL,
);
INSERT #Temp2
(
Assortment_Id,
CustomAttrID
)
SELECT
ACAV.Assortment_Id,
CAV.CustomAttrID
FROM #temp AS T
JOIN dbo.CustomAttributeValues AS CAV
ON CAV.Id = T.id
JOIN dbo.AssortmentCustomAttributeValues AS ACAV
ON T.id = ACAV.CustomAttributeValue_Id;
ALTER TABLE #Temp2
ADD CONSTRAINT PK_#Temp2_Assortment_Id_CustomAttrID
PRIMARY KEY CLUSTERED (Assortment_Id, CustomAttrID);
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:
SELECT
A.Id,
A.AssortmentId
FROM
(
SELECT
T.Assortment_Id
FROM #Temp2 AS T
GROUP BY
T.Assortment_Id
HAVING
COUNT_BIG(DISTINCT T.CustomAttrID) = @dist_ca_id
) AS DT
JOIN dbo.Assortments AS A
ON A.Id = DT.Assortment_Id
WHERE
A.AssortmentType = @asType
OPTION (RECOMPILE);
Kami dapat menulis ulang secara manual COUNT_BIG(DISTINCT...
sebagai yang sederhana COUNT_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.
#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.