Singkatnya, kami memperbarui tabel kecil orang dengan nilai dari tabel orang yang sangat besar. Dalam pengujian baru-baru ini, pembaruan ini membutuhkan waktu sekitar 5 menit untuk dijalankan.
Kami menemukan apa yang tampaknya seperti optimasi paling konyol yang mungkin, yang tampaknya bekerja dengan sempurna! Permintaan yang sama sekarang berjalan dalam waktu kurang dari 2 menit dan menghasilkan hasil yang sama, dengan sempurna.
Ini pertanyaannya. Baris terakhir ditambahkan sebagai "optimisasi". Mengapa penurunan tajam dalam waktu permintaan? Apakah kita melewatkan sesuatu? Bisakah ini menimbulkan masalah di masa depan?
UPDATE smallTbl
SET smallTbl.importantValue = largeTbl.importantValue
FROM smallTableOfPeople smallTbl
JOIN largeTableOfPeople largeTbl
ON largeTbl.birth_date = smallTbl.birthDate
AND DIFFERENCE(TRIM(smallTbl.last_name),TRIM(largeTbl.last_name)) = 4
AND DIFFERENCE(TRIM(smallTbl.first_name),TRIM(largeTbl.first_name)) = 4
WHERE smallTbl.importantValue IS NULL
-- The following line is "the optimization"
AND LEFT(TRIM(largeTbl.last_name), 1) IN ('a','à','á','b','c','d','e','è','é','f','g','h','i','j','k','l','m','n','o','ô','ö','p','q','r','s','t','u','ü','v','w','x','y','z','æ','ä','ø','å')
Catatan teknis: Kami menyadari bahwa daftar surat yang akan diuji mungkin memerlukan beberapa surat lagi. Kami juga menyadari margin kesalahan yang jelas ketika menggunakan "PERBEDAAN".
Rencana kueri (reguler): https://www.brentozar.com/pastetheplan/?id=rypV84y7V
Rencana kueri (dengan "optimasi"): https://www.brentozar.com/pastetheplan/?id=r1aC2my7E
AND LEFT(TRIM(largeTbl.last_name), 1) BETWEEN 'a' AND 'z' COLLATE LATIN1_GENERAL_CI_AI
harus melakukan apa yang Anda inginkan di sana tanpa mengharuskan Anda untuk membuat daftar semua karakter dan memiliki kode yang sulit dibacaWHERE
salah? Catatan khusus bahwa perbandingan mungkin peka terhadap huruf besar-kecil.Latin1_General_100_CI_AI
. Dan untuk SQL Server 2012 dan yang lebih baru (paling tidak melalui SQL Server 2019), yang terbaik adalah menggunakan kumpulan yang didukung karakter tambahan dalam versi tertinggi untuk lokal yang digunakan. Jadi ituLatin1_General_100_CI_AI_SC
dalam kasus ini. Versi> 100 (hanya bahasa Jepang sejauh ini) tidak memiliki (atau membutuhkan)_SC
(misJapanese_XJIS_140_CI_AI
.).Jawaban:
Itu tergantung pada data di tabel Anda, indeks Anda, .... Sulit dikatakan tanpa bisa membandingkan rencana eksekusi / statistik waktu + io +.
Perbedaan yang saya harapkan adalah pemfilteran ekstra terjadi sebelum GABUNG di antara dua tabel. Dalam contoh saya, saya mengubah pembaruan untuk memilih untuk menggunakan kembali tabel saya.
Rencana eksekusi dengan "optimasi"
Rencana eksekusi
Anda dengan jelas melihat operasi filter terjadi, dalam data pengujian saya tidak ada catatan di mana disaring dan akibatnya tidak ada perbaikan di mana dilakukan.
Rencana eksekusi, tanpa "optimasi"
Rencana eksekusi
Filter hilang, yang berarti bahwa kita harus bergantung pada gabungan untuk menyaring catatan yang tidak dibutuhkan.
Alasan lain Alasan / konsekuensi lain dari mengubah kueri bisa jadi, bahwa rencana eksekusi baru dibuat ketika mengubah kueri, yang kebetulan lebih cepat. Contoh dari ini adalah mesin memilih operator Bergabung yang berbeda, tetapi itu hanya menebak pada titik ini.
EDIT:
Klarifikasi setelah mendapatkan dua paket permintaan:
Permintaan membaca 550M Baris dari tabel besar, dan menyaringnya.
Berarti predikat adalah yang melakukan sebagian besar penyaringan, bukan predikat pencarian. Menghasilkan data yang sedang dibaca, tetapi tidak terlalu dikembalikan.
Membuat sql server menggunakan indeks yang berbeda (rencana kueri) / menambahkan indeks bisa menyelesaikannya.
Jadi mengapa kueri pengoptimalan tidak memiliki masalah yang sama?
Karena rencana kueri yang berbeda digunakan, dengan pemindaian alih-alih pencarian.
Tanpa melakukan apa pun, tetapi hanya mengembalikan 4M baris untuk bekerja dengannya.
Perbedaan selanjutnya
Mengabaikan perbedaan pembaruan (tidak ada yang diperbarui pada kueri yang dioptimalkan) kecocokan hash digunakan pada kueri yang dioptimalkan:
Alih-alih loop bersarang bergabung pada yang tidak dioptimalkan:
Nested loop adalah yang terbaik ketika satu meja kecil dan yang lainnya besar. Karena keduanya dekat dengan ukuran yang sama, saya berpendapat bahwa pertandingan hash adalah pilihan yang lebih baik dalam kasus ini.
Gambaran
Kueri yang dioptimalkan
Paket kueri yang dioptimalkan memiliki parallellism, menggunakan gabungan hash, dan perlu melakukan lebih sedikit penyaringan IO residual. Itu juga menggunakan bitmap untuk menghilangkan nilai-nilai kunci yang tidak bisa menghasilkan baris gabungan apa pun. (Juga tidak ada yang diperbarui)
Kueri yang tidak dioptimalkan Rencana kueri yang tidak Dioptimalkan tidak memiliki parallellism, menggunakan gabungan loop bersarang, dan perlu melakukan penyaringan IO residual pada catatan 550M. (Juga pembaruan sedang terjadi)
Apa yang dapat Anda lakukan untuk meningkatkan kueri yang tidak dioptimalkan?
Mengubah indeks untuk memiliki first_name & last_name dalam daftar kolom kunci:
CREATE INDEX IX_largeTableOfPeople_birth_date_first_name_last_name di dbo.largeTableOfPeople (birth_date, first_name, last_name) termasuk (id)
Tetapi karena penggunaan fungsi dan tabel ini menjadi besar ini mungkin bukan solusi yang optimal.
(HASH JOIN, MERGE JOIN)
ke kueriData uji + Pertanyaan yang digunakan
sumber
Tidak jelas bahwa permintaan kedua sebenarnya merupakan peningkatan.
Paket eksekusi berisi QueryTimeStats yang menunjukkan perbedaan yang jauh lebih dramatis daripada yang dinyatakan dalam pertanyaan.
Rencana lambat memiliki waktu berlalu
257,556 ms
(4 menit 17 detik). Rencana cepat memiliki waktu yang telah berlalu190,992 ms
(3 menit 11 detik) meskipun berjalan dengan tingkat paralelisme 3.Apalagi rencana kedua berjalan dalam database di mana tidak ada pekerjaan yang harus dilakukan setelah bergabung.
Paket Pertama
Paket kedua
Sehingga waktu tambahan bisa dijelaskan dengan pekerjaan yang diperlukan untuk memperbarui 3,5 juta baris (pekerjaan yang diperlukan dalam operator pembaruan untuk menemukan baris ini, mengunci halaman, menulis pembaruan ke halaman dan log transaksi tidak dapat diabaikan)
Jika ini sebenarnya dapat direproduksi ketika membandingkan suka dengan suka maka penjelasannya adalah bahwa Anda beruntung dalam hal ini.
Filter dengan 37
IN
kondisi hanya menghilangkan 51 baris dari 4.008.334 dalam tabel tetapi pengoptimal menganggap itu akan menghilangkan lebih banyakPerkiraan kardinalitas yang salah seperti itu biasanya merupakan hal yang buruk. Dalam hal ini menghasilkan rencana yang berbeda bentuk (dan paralel) yang tampaknya (?) Bekerja lebih baik untuk Anda meskipun tumpahan hash yang disebabkan oleh perkiraan yang terlalu rendah.
Tanpa
TRIM
SQL Server dapat mengkonversi ini ke interval rentang dalam histogram kolom dasar dan memberikan perkiraan yang jauh lebih akurat tetapi denganTRIM
itu hanya resor untuk menebak.Sifat tebakannya bisa beragam, tetapi perkiraan untuk satu predikat tunggal
LEFT(TRIM(largeTbl.last_name), 1)
dalam beberapa keadaan * hanya diperkirakantable_cardinality/estimated_number_of_distinct_column_values
.Saya tidak yakin persis keadaan apa - ukuran data tampaknya berperan. Saya dapat mereproduksi ini dengan tipe data panjang tetap yang lebar seperti di sini tetapi mendapat tebakan berbeda, lebih tinggi, dengan
varchar
(yang hanya menggunakan tebakan datar 10% dan diperkirakan 100.000 baris). @ Solomon Rutzky menunjukkan bahwa jikavarchar(100)
diisi dengan spasi tambahan seperti yang terjadi untukchar
estimasi yang lebih rendah digunakanThe
IN
daftar diperluas keOR
dan SQL Server menggunakan backoff eksponensial dengan maksimum 4 predikat dipertimbangkan. Jadi219.707
perkiraannya adalah sebagai berikut.sumber