TL; DR
Karena pertanyaan ini terus mendapatkan pandangan, saya akan meringkasnya di sini sehingga pendatang baru tidak harus menderita sejarah:
JOIN table t ON t.member = @value1 OR t.member = @value2 -- this is slow as hell
JOIN table t ON t.member = COALESCE(@value1, @value2) -- this is blazing fast
-- Note that here if @value1 has a value, @value2 is NULL, and vice versa
Saya menyadari ini mungkin bukan masalah semua orang, tetapi dengan menyoroti sensitivitas klausa ON, ini mungkin membantu Anda melihat ke arah yang benar. Bagaimanapun teks asli ada di sini untuk antropolog masa depan:
Teks asli
Pertimbangkan permintaan sederhana berikut (hanya 3 tabel yang terlibat)
SELECT
l.sku_id AS ProductId,
l.is_primary AS IsPrimary,
v1.category_name AS Category1,
v2.category_name AS Category2,
v3.category_name AS Category3,
v4.category_name AS Category4,
v5.category_name AS Category5
FROM category c4
JOIN category_voc v4 ON v4.category_id = c4.category_id and v4.language_code = 'en'
JOIN category c3 ON c3.category_id = c4.parent_category_id
JOIN category_voc v3 ON v3.category_id = c3.category_id and v3.language_code = 'en'
JOIN category c2 ON c2.category_id = c3.category_id
JOIN category_voc v2 ON v2.category_id = c2.category_id and v2.language_code = 'en'
JOIN category c1 ON c1.category_id = c2.parent_category_id
JOIN category_voc v1 ON v1.category_id = c1.category_id and v1.language_code = 'en'
LEFT OUTER JOIN category c5 ON c5.parent_category_id = c4.category_id
LEFT OUTER JOIN category_voc v5 ON v5.category_id = c5.category_id and v5.language_code = @lang
JOIN category_link l on l.sku_id IN (SELECT value FROM #Ids) AND
(
l.category_id = c4.category_id OR
l.category_id = c5.category_id
)
WHERE c4.[level] = 4 AND c4.version_id = 5
Ini adalah permintaan yang cukup sederhana, satu-satunya bagian yang membingungkan adalah gabungan kategori terakhir, ini cara ini karena kategori level 5 mungkin atau mungkin tidak ada. Di akhir kueri saya mencari info kategori per ID produk (SKU ID), dan di situlah tabel_link yang sangat besar masuk. Akhirnya, tabel #Ids hanyalah tabel temp yang berisi 10'000 Id.
Ketika dieksekusi, saya mendapatkan rencana eksekusi aktual berikut:
Seperti yang Anda lihat, hampir 90% dari waktu dihabiskan di Nested Loops (Inner Join). Berikut informasi tambahan tentang Nested Loops tersebut:
Perhatikan bahwa nama tabel tidak sama persis karena saya mengedit nama tabel kueri agar mudah dibaca, tetapi cukup mudah untuk mencocokkan (ads_alt_category = kategori). Apakah ada cara untuk mengoptimalkan permintaan ini? Juga perhatikan bahwa dalam produksi, tabel temp #Ids tidak ada, itu adalah Parameter yang Dihitung Tabel dari 10'000 Id yang sama diteruskan ke Prosedur Tersimpan.
Informasi tambahan:
- indeks kategori pada category_id dan parent_category_id
- indeks category_voc pada category_id, language_code
- indeks category_link di sku_id, category_id
Edit (dipecahkan)
Seperti yang ditunjukkan oleh jawaban yang diterima, masalahnya adalah klausa OR di category_link BERGABUNG. Namun, kode yang disarankan dalam jawaban yang diterima sangat lambat, bahkan lebih lambat dari kode aslinya. Solusi yang jauh lebih cepat dan juga lebih bersih adalah dengan mengganti kondisi JOIN saat ini dengan yang berikut:
JOIN category_link l on l.sku_id IN (SELECT value FROM @p1) AND l.category_id = COALESCE(c5.category_id, c4.category_id)
Tweak menit ini adalah solusi tercepat, diuji terhadap gabungan ganda dari jawaban yang diterima dan juga diuji terhadap CROSS BERLAKU seperti yang disarankan oleh valverij.
sumber
Jawaban:
Masalahnya muncul di bagian kode ini:
or
dalam kondisi join selalu mencurigakan. Satu saran adalah untuk membagi ini menjadi dua gabungan:Anda kemudian harus mengubah sisa kueri untuk menangani ini. . .
coalesce(l1.sku_id, l2.sku_id)
misalnya dalamselect
klausa.sumber
JOIN
keCROSS APPLY
denganIN
beralih keEXISTS
dalamAPPLY
'sWHERE
klausa.ON l.category_id = ISNULL(c5.category_id, c4.category_id
melakukan trik.coalesce()
mendorong pengoptimal ke arah yang benar.Seperti yang disebutkan pengguna lain, gabungan ini kemungkinan penyebabnya:
Selain membaginya menjadi beberapa gabungan, Anda juga dapat mencoba a
CROSS APPLY
Dari tautan MSDN di atas:
Pada dasarnya,
APPLY
ini seperti subquery yang memfilter rekaman di kanan terlebih dahulu, dan kemudian menerapkannya ke seluruh kueri Anda.Artikel ini melakukan pekerjaan yang sangat baik untuk menjelaskan apa itu dan kapan menggunakannya: http://explainextended.com/2009/07/16/inner-join-vs-cross-apply/
Penting untuk dicatat, bagaimanapun, bahwa
CROSS APPLY
tidak selalu berkinerja lebih cepat daripadaINNER JOIN
. Dalam banyak situasi, mungkin akan hampir sama. Dalam kasus yang jarang terjadi, saya benar-benar melihatnya lebih lambat (sekali lagi, ini semua tergantung pada struktur tabel Anda dan permintaan itu sendiri).Sebagai aturan umum, jika saya menemukan diri saya bergabung ke meja dengan pernyataan kondisional terlalu banyak, maka saya cenderung condong ke arah
APPLY
Juga catatan yang menyenangkan:
OUTER APPLY
akan bertindak seperti aLEFT JOIN
Juga, harap perhatikan pilihan saya untuk digunakan
EXISTS
daripadaIN
. Ketika melakukanIN
subquery, ingatlah bahwa itu akan mengembalikan set seluruh hasil, bahkan setelah telah menemukan nilai Anda. DenganEXISTS
, meskipun, itu akan menghentikan subquery begitu menemukan kecocokan.sumber
AND x.cat = c4.cat OR x.cat = c5.cat
denganx.cat = ISNULL(c5.cat, c4.cat)
dan menyingkirkan klausa IN menjadikan ini solusi tercepat kedua, dan layak mendapat upvote, karena cukup informatif.