[ Bagian 1 | Bagian 2 | Bagian 3 | Bagian 4 ]
MERGE
pernyataan (diperkenalkan di SQL Server 2008) memungkinkan kita untuk melakukan campuran INSERT
, UPDATE
, dan DELETE
operasi menggunakan satu pernyataan. Masalah Perlindungan Halloween untuk MERGE
sebagian besar merupakan kombinasi dari persyaratan operasi individu, tetapi ada beberapa perbedaan penting dan beberapa pengoptimalan menarik yang hanya berlaku untuk MERGE
.
Menghindari Masalah Halloween dengan MERGE
Kita mulai dengan melihat kembali contoh Demo dan Staging dari bagian dua:
CREATE TABLE dbo.Demo ( SomeKey integer NOT NULL, CONSTRAINT PK_Demo PRIMARY KEY (SomeKey) ); CREATE TABLE dbo.Staging ( SomeKey integer NOT NULL ); INSERT dbo.Staging (SomeKey) VALUES (1234), (1234); CREATE NONCLUSTERED INDEX c ON dbo.Staging (SomeKey); INSERT dbo.Demo SELECT s.SomeKey FROM dbo.Staging AS s WHERE NOT EXISTS ( SELECT 1 FROM dbo.Demo AS d WHERE d.SomeKey = s.SomeKey );
Seperti yang Anda ingat, contoh ini digunakan untuk menunjukkan bahwa INSERT
membutuhkan Perlindungan Halloween ketika tabel target penyisipan juga dirujuk dalam SELECT
bagian dari kueri (EXISTS
klausa dalam hal ini). Perilaku yang benar untuk INSERT
pernyataan di atas adalah mencoba menambahkan keduanya 1234 nilai, dan akibatnya gagal dengan PRIMARY KEY
pelanggaran. Tanpa pemisahan fase, INSERT
akan salah menambahkan satu nilai, menyelesaikan tanpa kesalahan yang dilemparkan.
Rencana eksekusi INSERT
Kode di atas memiliki satu perbedaan dari yang digunakan di bagian dua; indeks nonclustered pada tabel Staging telah ditambahkan. INSERT
rencana eksekusi tetap membutuhkan Perlindungan Halloween:
Rencana eksekusi MERGE
Sekarang coba sisipan logis yang sama yang diekspresikan menggunakan MERGE
sintaks:
MERGE dbo.Demo AS d USING dbo.Staging AS s ON s.SomeKey = d.SomeKey WHEN NOT MATCHED BY TARGET THEN INSERT (SomeKey) VALUES (s.SomeKey);
Jika Anda tidak terbiasa dengan sintaks, logika yang ada untuk membandingkan baris di tabel Staging dan Demo pada nilai SomeKey, dan jika tidak ada baris yang cocok ditemukan di tabel target (Demo), kami menyisipkan baris baru. Ini memiliki semantik yang persis sama dengan INSERT...WHERE NOT EXISTS
sebelumnya kode, tentu saja. Namun, rencana eksekusinya sangat berbeda:
Perhatikan kurangnya Eager Table Spool dalam rencana ini. Meskipun demikian, kueri masih menghasilkan pesan kesalahan yang benar. Tampaknya SQL Server telah menemukan cara untuk mengeksekusi MERGE
rencanakan secara iteratif sambil menghormati pemisahan fase logis yang disyaratkan oleh standar SQL.
Optimasi pengisian lubang
Dalam situasi yang tepat, pengoptimal SQL Server dapat mengenali bahwa MERGE
pernyataan adalah mengisi lubang , yang merupakan cara lain untuk mengatakan bahwa pernyataan hanya menambahkan baris jika ada celah yang ada di kunci tabel target.
Agar pengoptimalan ini dapat diterapkan, nilai-nilai yang digunakan dalam WHEN NOT MATCHED BY TARGET
klausa harus tepat cocokkan dengan ON
bagian dari USING
ayat. Juga, tabel target harus memiliki kunci unik (persyaratan yang dipenuhi oleh PRIMARY KEY
dalam kasus ini). Jika persyaratan ini terpenuhi, MERGE
pernyataan tidak memerlukan perlindungan dari Masalah Halloween.
Tentu saja, MERGE
pernyataan secara logis tidak lebih atau kurang mengisi lubang daripada INSERT...WHERE NOT EXISTS
sintaksis. Perbedaannya adalah pengoptimal memiliki kendali penuh atas penerapan MERGE
pernyataan, sedangkan INSERT
sintaks akan mengharuskannya untuk mempertimbangkan semantik kueri yang lebih luas. Manusia dapat dengan mudah melihat bahwa INSERT
juga mengisi lubang, tetapi pengoptimal tidak memikirkan hal-hal dengan cara yang sama seperti kita.
Untuk mengilustrasikan pencocokan persis persyaratan yang saya sebutkan, pertimbangkan sintaks kueri berikut, yang tidak manfaat dari optimasi pengisian lubang. Hasilnya adalah Perlindungan Halloween penuh yang disediakan oleh Eager Table Spool:
MERGE dbo.Demo AS d USING dbo.Staging AS s ON s.SomeKey = d.SomeKey WHEN NOT MATCHED THEN INSERT (SomeKey) VALUES (s.SomeKey * 1);
Satu-satunya perbedaan adalah perkalian dengan satu di VALUES
klausa – sesuatu yang tidak mengubah logika kueri, tetapi cukup untuk mencegah penerapan pengoptimalan pengisian lubang.
Pengisian lubang dengan Loop Bersarang
Pada contoh sebelumnya, pengoptimal memilih untuk menggabungkan tabel menggunakan gabung Gabung. Pengoptimalan pengisian lubang juga dapat diterapkan jika gabungan Nested Loops dipilih, tetapi ini memerlukan jaminan keunikan ekstra pada tabel sumber, dan pencarian indeks di sisi dalam gabungan. Untuk melihat ini beraksi, kita dapat menghapus data staging yang ada, menambahkan keunikan pada indeks nonclustered, dan mencoba MERGE
lagi:
-- Remove existing duplicate rows TRUNCATE TABLE dbo.Staging; -- Convert index to unique CREATE UNIQUE NONCLUSTERED INDEX c ON dbo.Staging (SomeKey) WITH (DROP_EXISTING = ON); -- Sample data INSERT dbo.Staging (SomeKey) VALUES (1234), (5678); -- Hole-filling merge MERGE dbo.Demo AS d USING dbo.Staging AS s ON s.SomeKey = d.SomeKey WHEN NOT MATCHED THEN INSERT (SomeKey) VALUES (s.SomeKey);
Rencana eksekusi yang dihasilkan lagi-lagi menggunakan pengoptimalan pengisian lubang untuk menghindari Perlindungan Halloween, menggunakan gabungan loop bersarang dan pencarian sisi dalam ke tabel target:
Menghindari traversal indeks yang tidak perlu
Di mana optimasi pengisian lubang berlaku, mesin juga dapat menerapkan optimasi lebih lanjut. Itu dapat mengingat posisi indeks saat ini saat membaca tabel target (memproses satu baris pada satu waktu, ingat) dan menggunakan kembali informasi itu saat melakukan penyisipan, alih-alih mencari b-tree untuk menemukan lokasi penyisipan. Alasannya adalah bahwa posisi baca saat ini sangat mungkin berada di halaman yang sama di mana baris baru harus disisipkan. Memeriksa apakah baris tersebut memang milik halaman ini sangat cepat, karena hanya melibatkan pemeriksaan kunci terendah dan tertinggi yang saat ini disimpan di sana.
Kombinasi menghilangkan Eager Table Spool dan menyimpan navigasi indeks per baris dapat memberikan manfaat yang signifikan dalam beban kerja OLTP, asalkan rencana eksekusi diambil dari cache. Biaya kompilasi untuk MERGE
pernyataan agak lebih tinggi daripada untuk INSERT
, UPDATE
dan DELETE
, jadi rencana penggunaan kembali merupakan pertimbangan penting. Hal ini juga membantu untuk memastikan bahwa halaman memiliki ruang kosong yang cukup untuk menampung baris baru, menghindari pemisahan halaman. Ini biasanya dicapai melalui pemeliharaan indeks normal dan penetapan FILLFACTOR
yang sesuai .
Saya menyebutkan beban kerja OLTP, yang biasanya menampilkan sejumlah besar perubahan yang relatif kecil, karena MERGE
optimasi mungkin bukan pilihan yang baik di mana sejumlah besar baris diproses per pernyataan. Pengoptimalan lain seperti INSERTs
yang di-log minimal saat ini tidak dapat digabungkan dengan pengisian lubang. Seperti biasa, karakteristik kinerja harus dijadikan tolok ukur untuk memastikan manfaat yang diharapkan terwujud.
Pengoptimalan pengisian lubang untuk MERGE
sisipan dapat digabungkan dengan pembaruan dan penghapusan menggunakan MERGE
tambahan klausa; setiap operasi pengubahan data dinilai secara terpisah untuk Masalah Halloween.
Menghindari bergabung
Optimalisasi terakhir yang akan kita lihat dapat diterapkan di mana MERGE
pernyataan berisi operasi pembaruan dan penghapusan serta sisipan pengisi lubang, dan tabel target memiliki indeks berkerumun yang unik. Contoh berikut menunjukkan MERGE
yang umum pola di mana baris yang tidak cocok dimasukkan, dan baris yang cocok diperbarui atau dihapus tergantung pada kondisi tambahan:
CREATE TABLE #T ( col1 integer NOT NULL, col2 integer NOT NULL, CONSTRAINT PK_T PRIMARY KEY (col1) ); CREATE TABLE #S ( col1 integer NOT NULL, col2 integer NOT NULL, CONSTRAINT PK_S PRIMARY KEY (col1) ); INSERT #T (col1, col2) VALUES (1, 50), (3, 90); INSERT #S (col1, col2) VALUES (1, 40), (2, 80), (3, 90);
MERGE
pernyataan yang diperlukan untuk membuat semua perubahan yang diperlukan sangat ringkas:
MERGE #T AS t USING #S AS s ON t.col1 = s.col1 WHEN NOT MATCHED THEN INSERT VALUES (s.col1, s.col2) WHEN MATCHED AND t.col2 - s.col2 = 0 THEN DELETE WHEN MATCHED THEN UPDATE SET t.col2 -= s.col2;
Rencana eksekusinya cukup mengejutkan:
Tidak ada Perlindungan Halloween, tidak ada gabungan antara tabel sumber dan target, dan tidak sering Anda akan melihat operator Sisipan Indeks Cluster diikuti oleh Penggabungan Indeks Cluster ke tabel yang sama. Ini adalah pengoptimalan lain yang ditargetkan pada beban kerja OLTP dengan penggunaan ulang paket tinggi dan pengindeksan yang sesuai.
Idenya adalah untuk membaca satu baris dari tabel sumber dan segera mencoba memasukkannya ke dalam target. Jika pelanggaran kunci terjadi, kesalahan akan ditekan, operator Sisipkan menampilkan baris yang bertentangan yang ditemukannya, dan baris tersebut kemudian diproses untuk operasi pembaruan atau penghapusan menggunakan operator Gabungkan paket seperti biasa.
Jika penyisipan asli berhasil (tanpa pelanggaran kunci) pemrosesan dilanjutkan dengan baris berikutnya dari sumber (operator Gabung hanya memproses pembaruan dan penghapusan). Pengoptimalan ini terutama menguntungkan MERGE
kueri di mana sebagian besar baris sumber menghasilkan sisipan. Sekali lagi, tolok ukur yang cermat diperlukan untuk memastikan kinerja lebih baik daripada menggunakan pernyataan terpisah.
Ringkasan
MERGE
pernyataan memberikan beberapa peluang pengoptimalan yang unik. Dalam situasi yang tepat, ini dapat menghindari kebutuhan untuk menambahkan Perlindungan Halloween eksplisit dibandingkan dengan INSERT
yang setara operasi, atau bahkan mungkin kombinasi INSERT
, UPDATE
, dan DELETE
pernyataan. Tambahan MERGE
-optimasi spesifik dapat menghindari traversal indeks b-tree yang biasanya diperlukan untuk menemukan posisi penyisipan untuk baris baru, dan juga dapat menghindari kebutuhan untuk menggabungkan tabel sumber dan target sepenuhnya.
Di bagian akhir seri ini, kita akan melihat bagaimana alasan pengoptimal kueri tentang perlunya perlindungan Halloween, dan mengidentifikasi beberapa trik lain yang dapat diterapkan untuk menghindari perlunya menambahkan Eager Table Spool ke rencana eksekusi yang mengubah data.
[ Bagian 1 | Bagian 2 | Bagian 3 | Bagian 4 ]