Saya tahu bahwa melakukan COALESCE
pada beberapa kolom dan bergabung dengannya bukanlah praktik yang baik.
Menghasilkan kardinalitas dan estimasi distribusi yang baik cukup sulit ketika skema adalah 3NF + (dengan kunci dan kendala) dan kueri bersifat relasional dan terutama SPJG (seleksi-proyeksi-gabung-grup oleh). Model CE dibangun berdasarkan prinsip-prinsip tersebut. Fitur yang lebih tidak biasa atau non-relasional ada dalam kueri, semakin dekat satu dengan batas-batas apa kardinalitas dan kerangka kerja selektivitas dapat menangani. Pergi terlalu jauh dan CE akan menyerah dan menebak .
Sebagian besar contoh MCVE adalah SPJ sederhana (tanpa G), meskipun dengan equijoins luar yang lebih dominan (dimodelkan sebagai gabungan dalam ditambah anti-semijoin) daripada equijoin dalam yang lebih sederhana (atau semijoin). Semua relasi memiliki kunci, meskipun tidak ada kunci asing atau kendala lainnya. Semua kecuali satu dari gabungan adalah satu-ke-banyak, yang bagus.
Pengecualian adalah banyak-ke-banyak gabungan luar antara X_DETAIL_1
dan X_DETAIL_LINK
. Satu-satunya fungsi gabungan ini di MCVE adalah berpotensi menduplikasi baris X_DETAIL_1
. Ini adalah hal yang tidak biasa .
Predikat kesetaraan sederhana (pilihan) dan operator skalar juga lebih baik. Sebagai contoh, atribut bandingkan-sama / konstan biasanya berfungsi dengan baik dalam model. Relatif "mudah" untuk memodifikasi histogram dan statistik frekuensi untuk mencerminkan penerapan predikat tersebut.
COALESCE
dibangun CASE
, yang pada gilirannya diimplementasikan secara internal sebagai IIF
(dan ini benar sebelum IIF
muncul dalam bahasa Transact-SQL). Model CE IIF
sebagai UNION
dengan dua anak yang saling eksklusif, masing-masing terdiri dari proyek pada seleksi pada relasi input. Masing-masing komponen yang tercantum memiliki dukungan model, sehingga menggabungkannya relatif mudah. Meski begitu, semakin banyak satu lapisan abstraksi, semakin tidak akurat hasil akhirnya - alasan mengapa rencana pelaksanaan yang lebih besar cenderung kurang stabil dan dapat diandalkan.
ISNULL
, di sisi lain, intrinsik ke mesin. Itu tidak dibangun menggunakan komponen yang lebih mendasar. Menerapkan efek ISNULL
ke histogram, misalnya, semudah mengganti langkah untuk NULL
nilai (dan memadatkan sesuai kebutuhan). Masih relatif buram, karena operator skalar pergi, dan sebaiknya dihindari jika memungkinkan. Namun demikian, secara umum ini lebih ramah-pengoptimal (kurang pengoptimal-tidak ramah) daripada CASE
alternatif berbasis.
CE (70 dan 120+) sangat kompleks, bahkan oleh standar SQL Server. Ini bukan kasus menerapkan logika sederhana (dengan formula rahasia) untuk setiap operator. CE tahu tentang kunci dan dependensi fungsional; ia tahu cara memperkirakan menggunakan frekuensi, statistik multivarian, dan histogram; dan ada banyak sekali kasus khusus, penyempurnaan, check & balance, dan struktur pendukung. Ini sering memperkirakan misalnya bergabung dalam berbagai cara (frekuensi, histogram) dan memutuskan hasil atau penyesuaian berdasarkan perbedaan antara keduanya.
Satu hal dasar terakhir yang harus dicakup: Estimasi kardinalitas awal berjalan untuk setiap operasi di pohon kueri, dari bawah ke atas. Selektivitas dan kardinalitas diturunkan untuk operator daun terlebih dahulu (hubungan dasar). Informasi histogram dan kepadatan / frekuensi yang dimodifikasi diturunkan untuk operator induk. Semakin jauh kita melanjutkan, semakin rendah kualitas estimasi cenderung karena kesalahan cenderung menumpuk.
Estimasi komprehensif awal tunggal ini memberikan titik awal, dan terjadi jauh sebelum pertimbangan diberikan pada rencana eksekusi akhir (ini terjadi jauh sebelum tahap kompilasi rencana sepele). Pohon kueri pada titik ini cenderung mencerminkan bentuk kueri tertulis dengan cukup dekat (meskipun dengan subqueries dihapus, dan penyederhanaan diterapkan dll.)
Segera setelah estimasi awal, SQL Server melakukan heuristic join reordering, yang secara longgar mencoba menyusun ulang pohon untuk menempatkan tabel yang lebih kecil dan selektivitas tinggi bergabung terlebih dahulu. Ini juga mencoba untuk memposisikan sambungan dalam sebelum sambungan luar dan produk silang. Kemampuannya tidak luas; upayanya tidak lengkap; dan tidak mempertimbangkan biaya fisik (karena belum ada - hanya informasi statistik dan informasi metadata yang ada). Menyusun ulang heuristik paling berhasil pada pohon equijoin batin sederhana. Itu ada untuk memberikan titik awal yang "lebih baik" untuk optimasi berbasis biaya.
Mengapa perkiraan gabungan kardinalitas ini begitu besar?
MCVE memiliki " banyak-ke-banyak bergabung " tidak biasa "sebagian besar , dan equi bergabung dengan COALESCE
predikat. Pohon operator juga memiliki sambungan dalam yang terakhir , yang disusun ulang dengan heuristik tidak dapat memindahkan pohon ke posisi yang lebih disukai. Mengesampingkan semua skalar dan proyeksi, pohon gabungan adalah:
LogOp_Join [ Card=4.52803e+009 ]
LogOp_LeftOuterJoin [ Card=481577 ]
LogOp_LeftOuterJoin [ Card=481577 ]
LogOp_LeftOuterJoin [ Card=481577 ]
LogOp_LeftOuterJoin [ Card=481577 ]
LogOp_Get TBL: X_DRIVING_TABLE(alias TBL: dt) [ Card=481577 ]
LogOp_Get TBL: X_DETAIL_1(alias TBL: d1) [ Card=70 ]
LogOp_Get TBL: X_DETAIL_LINK(alias TBL: lnk) [ Card=47 ]
LogOp_Get TBL: X_DETAIL_2(alias TBL: d2) X_DETAIL_2 [ Card=119 ]
LogOp_Get TBL: X_DETAIL_3(alias TBL: d3) X_DETAIL_3 [ Card=281 ]
LogOp_Get TBL: X_LAST_TABLE(alias TBL: lst) X_LAST_TABLE [ Card=94025 ]
Perhatikan perkiraan akhir yang salah sudah ada. Ini dicetak sebagai Card=4.52803e+009
dan disimpan secara internal sebagai nilai floating point presisi ganda 4,5280277425e + 9 (4528027742.5 dalam desimal).
Tabel turunan dalam kueri asli telah dihapus, dan proyeksi dinormalisasi. Representasi SQL dari pohon tempat kardinalitas awal dan estimasi selektivitas dilakukan adalah:
SELECT
PRIMARY_ID = COALESCE(d1.JOIN_ID, d2.JOIN_ID, d3.JOIN_ID)
FROM X_DRIVING_TABLE dt
LEFT OUTER JOIN X_DETAIL_1 d1
ON dt.ID = d1.ID
LEFT OUTER JOIN X_DETAIL_LINK lnk
ON d1.LINK_ID = lnk.LINK_ID
LEFT OUTER JOIN X_DETAIL_2 d2
ON dt.ID = d2.ID
LEFT OUTER JOIN X_DETAIL_3 d3
ON dt.ID = d3.ID
INNER JOIN X_LAST_TABLE lst
ON lst.JOIN_ID = COALESCE(d1.JOIN_ID, d2.JOIN_ID, d3.JOIN_ID)
(Sebagai tambahan, yang diulang COALESCE
juga hadir dalam rencana akhir - sekali dalam Skalarat Hitung terakhir, dan sekali di sisi bagian dalam gabungan bagian dalam).
Perhatikan akhir bergabung. Gabungan dalam ini adalah (menurut definisi) produk kartesius X_LAST_TABLE
dan hasil gabungan sebelumnya, dengan pilihan (gabungan predikat) dari yang lst.JOIN_ID = COALESCE(d1.JOIN_ID, d2.JOIN_ID, d3.JOIN_ID)
diterapkan. Kardinalitas produk kartesius hanyalah 481577 * 94025 = 45280277425.
Untuk itu, kita perlu menentukan dan menerapkan selektivitas predikat. Kombinasi COALESCE
pohon yang diperluas buram (dalam hal UNION
dan IIF
, ingat) bersama-sama dengan dampak pada informasi utama, histogram turunan dan frekuensi dari gabungan luar-ke-luar yang "tidak biasa" sebagian besar-banyak-banyak-banyak yang dikombinasikan, berarti CE tidak mampu memperoleh estimasi yang dapat diterima dengan salah satu cara normal.
Akibatnya, ia memasuki Logika Tebak. Logika tebakan cukup kompleks, dengan lapisan tebakan "dididik" dan algoritme tebak "tidak-terdidik" dicoba. Jika tidak ada dasar yang lebih baik untuk tebakan ditemukan, model menggunakan tebakan terakhir, yang untuk perbandingan kesetaraan adalah: sqllang!x_Selectivity_Equal
= selektivitas tetap 0,1 (tebakan 10%):
-- the moment of doom
movsd xmm0,mmword ptr [sqllang!x_Selectivity_Equal
Hasilnya adalah selektivitas 0,1 pada produk kartesius: 481577 * 94025 * 0,1 = 4528027742,5 (~ 4,52803e + 009) seperti yang disebutkan sebelumnya.
Menulis ulang
Ketika gabungan yang bermasalah dikomentari , perkiraan yang lebih baik dihasilkan karena selektivitas tetap "tebakan terakhir" dihindari (informasi utama dipertahankan oleh gabungan 1-M). Kualitas taksiran masih rendah, karena COALESCE
predikat gabungan sama sekali tidak ramah CE. Perkiraan yang direvisi setidaknya terlihat lebih masuk akal bagi manusia, saya kira.
Ketika kueri ditulis dengan gabungan luar ke X_DETAIL_LINK
ditempatkan terakhir , pemesanan ulang heuristik dapat menukar dengan gabungan akhir batin X_LAST_TABLE
. Meletakkan sambungan dalam tepat di sebelah masalah sambungan luar memberikan kemampuan terbatas pada pemesanan ulang awal kesempatan untuk meningkatkan estimasi akhir, karena efek dari sambungan luar banyak-ke-banyak yang "tidak biasa" kebanyakan-ke-banyak yang banyak muncul setelah estimasi selektivitas rumit untuk COALESCE
. Sekali lagi, perkiraannya sedikit lebih baik dari perkiraan yang pasti, dan mungkin tidak akan tahan terhadap pemeriksaan silang yang ditentukan di pengadilan.
Menyusun ulang campuran gabungan dalam dan luar adalah sulit dan memakan waktu (bahkan optimisasi penuh tahap 2 hanya mencoba subset terbatas dari gerakan teoritis).
Sarang yang ISNULL
disarankan dalam jawaban Max Vernon berhasil menghindari tebakan tetap bail-out, tetapi estimasi akhir adalah nol baris yang mustahil (terangkat ke satu baris untuk kesopanan). Ini mungkin juga merupakan tebakan tetap 1 baris, untuk semua basis statistik yang dimiliki perhitungan.
Saya mengharapkan perkiraan kardinalitas gabungan antara 0 dan 481577 baris.
Ini adalah ekspektasi yang masuk akal, bahkan jika seseorang menerima bahwa estimasi kardinalitas dapat terjadi pada waktu yang berbeda (selama optimasi berbasis biaya) pada subtree yang secara fisik berbeda, tetapi secara logis dan semantik identik - dengan rencana akhir menjadi semacam yang terbaik yang dijahit bersama. terbaik (per grup memo). Kurangnya jaminan konsistensi seluruh rencana tidak berarti bahwa individu bergabung harus dapat merusak kehormatan, saya mengerti.
Di sisi lain, jika kita berakhir pada tebakan pilihan terakhir , harapan sudah hilang, jadi mengapa repot-repot. Kami mencoba semua trik yang kami tahu, dan menyerah. Jika tidak ada yang lain, estimasi akhir liar adalah tanda peringatan yang bagus bahwa tidak semuanya berjalan dengan baik di dalam CE selama kompilasi dan optimalisasi permintaan ini.
Ketika saya mencoba MCVE, 120+ CE menghasilkan estimasi akhir baris nol (= 1) (seperti yang bersarang ISNULL
) untuk kueri asli, yang sama tidak dapat diterima dengan cara berpikir saya.
Solusi nyata mungkin melibatkan perubahan desain, untuk memungkinkan equi-gabung sederhana tanpa COALESCE
atau ISNULL
, dan idealnya kunci asing & batasan lain yang berguna untuk kompilasi permintaan.
bigint
alih-alihdecimal(18, 0)
Anda akan mendapatkan manfaat: 1) gunakan 8 byte, bukan 9 untuk setiap nilai, dan 2) gunakan tipe data yang sebanding dengan byte alih-alih tipe data yang dikemas, yang dapat memiliki implikasi untuk waktu CPU saat membandingkan nilai.