Postingan sebelumnya dalam seri ini menunjukkan bagaimana pernyataan T-SQL berjalan di bawah isolasi snapshot berkomitmen baca (RCSI ) biasanya melihat tampilan snapshot dari kondisi database yang dikomit seperti saat pernyataan mulai dieksekusi. Itu adalah deskripsi yang bagus tentang cara kerja sesuatu untuk pernyataan yang membaca data, tetapi ada perbedaan penting untuk pernyataan yang berjalan di bawah RCSI yang memodifikasi baris yang ada .
Saya menekankan modifikasi baris yang ada di atas, karena pertimbangan berikut hanya berlaku untuk UPDATE
dan DELETE
operasi (dan tindakan yang sesuai dari MERGE
penyataan). Untuk lebih jelasnya, INSERT
pernyataan secara khusus dikecualikan dari perilaku yang akan saya jelaskan karena sisipan tidak mengubah yang ada data.
Perbarui kunci dan versi baris
Perbedaan pertama adalah bahwa perbarui dan hapus pernyataan jangan membaca versi baris di bawah RCSI saat mencari baris sumber untuk dimodifikasi. Perbarui dan hapus pernyataan di bawah RCSI alih-alih dapatkan kunci pembaruan saat mencari baris yang memenuhi syarat. Menggunakan kunci pembaruan memastikan bahwa operasi pencarian menemukan baris untuk dimodifikasi menggunakan data komitmen terbaru .
Tanpa kunci pembaruan, pencarian akan didasarkan pada versi kumpulan data yang mungkin kedaluwarsa (data yang dikomit seperti saat pernyataan modifikasi data dimulai). Ini mungkin mengingatkan Anda pada contoh pemicu yang kita lihat terakhir kali, di mana READCOMMITTEDLOCK
petunjuk digunakan untuk kembali dari RCSI ke implementasi penguncian dari isolasi baca yang dilakukan. Petunjuk itu diperlukan dalam contoh itu untuk menghindari mendasarkan tindakan penting pada informasi kedaluwarsa. Jenis penalaran yang sama digunakan di sini. Satu perbedaan adalah bahwa READCOMMITTEDLOCK
petunjuk memperoleh kunci bersama alih-alih memperbarui kunci. Selain itu, SQL Server secara otomatis memperoleh kunci pembaruan untuk melindungi modifikasi data di bawah RCSI tanpa mengharuskan kami menambahkan petunjuk eksplisit.
Mengambil kunci pembaruan juga memastikan bahwa pernyataan pembaruan atau penghapusan akan memblokir jika menemukan kunci yang tidak kompatibel, misalnya kunci eksklusif yang melindungi modifikasi data dalam penerbangan yang dilakukan oleh transaksi bersamaan lainnya.
Komplikasi tambahan adalah bahwa perilaku yang dimodifikasi hanya berlaku ke tabel yang menjadi target dari operasi pembaruan atau penghapusan. Tabel lainnya di sama hapus atau perbarui pernyataan, termasuk referensi tambahan ke tabel target, terus gunakan versi baris .
Beberapa contoh mungkin diperlukan untuk membuat perilaku yang membingungkan ini sedikit lebih jelas…
Pengaturan Pengujian
Skrip berikut memastikan kita semua siap untuk menggunakan RCSI, membuat tabel sederhana, dan menambahkan dua baris contoh ke dalamnya:
ALTER DATABASE Sandpit SET READ_COMMITTED_SNAPSHOT ON WITH ROLLBACK IMMEDIATE; GO SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO CREATE TABLE dbo.Test ( RowID integer PRIMARY KEY, Data integer NOT NULL ); GO INSERT dbo.Test (RowID, Data) VALUES (1, 1234), (2, 2345);
Langkah selanjutnya harus dijalankan dalam sesi terpisah . Ini memulai transaksi dan menghapus kedua baris dari tabel pengujian (tampak aneh, tapi ini semua akan segera masuk akal):
BEGIN TRANSACTION; DELETE dbo.Test WHERE RowID IN (1, 2);
Perhatikan bahwa transaksi sengaja dibiarkan terbuka . Ini mempertahankan kunci eksklusif pada kedua baris yang dihapus (bersama dengan kunci eksklusif maksud biasa pada halaman yang berisi dan tabel itu sendiri) karena kueri di bawah ini dapat digunakan untuk menunjukkan:
SELECT resource_type, resource_description, resource_associated_entity_id, request_mode, request_status FROM sys.dm_tran_locks WHERE request_session_id = @@SPID;
Tes Pilih
Beralih kembali ke sesi awal , hal pertama yang ingin saya tunjukkan adalah bahwa pernyataan pilih reguler menggunakan RCSI masih melihat dua baris dihapus. Kueri pemilihan di bawah ini menggunakan versi baris untuk mengembalikan data komitmen terbaru pada saat pernyataan dimulai:
SELECT * FROM dbo.Test;
Jika hal itu tampak mengejutkan, ingatlah bahwa menampilkan baris sebagai terhapus berarti menampilkan tampilan data yang tidak dikomit, yang tidak diizinkan pada isolasi berkomitmen baca.
Uji Penghapusan
Meskipun uji pilih berhasil, upaya untuk menghapus baris yang sama dari sesi saat ini akan diblokir. Anda mungkin membayangkan pemblokiran ini terjadi saat operasi mencoba memperoleh eksklusif terkunci, tapi bukan itu masalahnya.
Penghapusan tidak menggunakan versi baris untuk menemukan baris yang akan dihapus; ia mencoba untuk mendapatkan kunci pembaruan sebagai gantinya. Kunci pembaruan tidak kompatibel dengan kunci baris eksklusif yang dipegang oleh sesi dengan transaksi terbuka, sehingga kueri memblokir:
DELETE dbo.Test WHERE RowID IN (1, 2);
Perkiraan rencana kueri untuk pernyataan ini menunjukkan bahwa baris yang akan dihapus diidentifikasi oleh operasi pencarian reguler sebelum operator terpisah melakukan penghapusan yang sebenarnya:
Kita dapat melihat kunci yang ditahan pada tahap ini dengan menjalankan kueri penguncian yang sama seperti sebelumnya (dari sesi lain) mengingat untuk mengubah referensi SPID ke yang digunakan oleh kueri yang diblokir. Hasilnya terlihat seperti ini:
Kueri penghapusan kami diblokir di operator Clustered Index Seek, yang menunggu untuk memperoleh kunci pembaruan untuk membaca data. Ini menunjukkan bahwa menemukan baris yang akan dihapus di bawah RCSI memperoleh kunci pembaruan daripada membaca data berversi yang berpotensi basi. Ini juga menunjukkan bahwa pemblokiran bukan karena bagian penghapusan dari operasi yang menunggu untuk memperoleh kunci eksklusif.
Uji Pembaruan
Batalkan kueri yang diblokir dan coba pembaruan berikut sebagai gantinya:
UPDATE dbo.Test SET Data = Data + 1000 WHERE RowID IN (1, 2);
Perkiraan rencana eksekusi mirip dengan yang terlihat pada tes hapus:
Skalar Hitung ada untuk menentukan hasil penambahan 1000 ke nilai kolom Data saat ini di setiap baris, yang dibaca oleh Pencarian Indeks Cluster. Pernyataan ini juga akan memblokir saat dijalankan, karena kunci pembaruan yang diminta oleh operasi baca. Tangkapan layar di bawah menunjukkan kunci yang ditahan saat kueri memblokir:
Seperti sebelumnya, kueri diblokir saat pencarian, menunggu kunci eksklusif yang tidak kompatibel dilepaskan sehingga kunci pembaruan dapat diperoleh.
Uji Sisipan
Pengujian berikutnya menampilkan pernyataan yang menyisipkan baris baru ke dalam tabel pengujian kami, menggunakan nilai kolom Data dari baris yang ada dengan ID 1 di tabel. Ingatlah bahwa baris ini masih dikunci secara eksklusif oleh sesi dengan transaksi terbuka:
INSERT dbo.Test (RowID, Data) SELECT 3, Data FROM dbo.Test WHERE RowID = 1;
Rencana eksekusi sekali lagi mirip dengan tes sebelumnya:
Kali ini, kueri tidak diblokir . Ini menunjukkan bahwa kunci pembaruan tidak diperoleh saat membaca data untuk sisipan. Kueri ini malah menggunakan versi baris untuk memperoleh nilai kolom Data untuk baris yang baru dimasukkan. Kunci pembaruan tidak diperoleh karena pernyataan ini tidak menemukan baris apa pun untuk diubah , itu hanya membaca data untuk digunakan di sisipan.
Kita dapat melihat baris baru ini di tabel menggunakan kueri uji pilih dari sebelumnya:
Perhatikan bahwa kami adalah dapat memperbarui dan menghapus baris baru (yang akan memerlukan kunci pembaruan) karena tidak ada kunci eksklusif yang saling bertentangan. Sesi dengan transaksi terbuka hanya memiliki kunci eksklusif pada baris 1 dan 2:
-- Update the new row UPDATE dbo.Test SET Data = 9999 WHERE RowID = 3; -- Show the data SELECT * FROM dbo.Test; -- Delete the new row DELETE dbo.Test WHERE RowID = 3;
Pengujian ini mengkonfirmasi bahwa masukkan pernyataan tidak memperoleh kunci pembaruan saat membaca , karena tidak seperti pembaruan dan penghapusan, mereka tidak memodifikasi baris yang ada. Bagian membaca dari sisipan pernyataan menggunakan perilaku versi baris RCSI normal.
Tes referensi ganda
Saya sebutkan sebelumnya bahwa hanya referensi tabel tunggal yang digunakan untuk menemukan baris yang akan dimodifikasi yang memperoleh kunci pembaruan; tabel lain dalam pernyataan pembaruan atau penghapusan yang sama masih membaca versi baris. Sebagai kasus khusus dari prinsip umum tersebut, pernyataan modifikasi data dengan banyak referensi ke tabel yang sama hanya menerapkan kunci pembaruan pada satu instance digunakan untuk mencari baris yang akan dimodifikasi. Tes terakhir ini menggambarkan perilaku yang lebih kompleks ini, selangkah demi selangkah.
Hal pertama yang kita perlukan adalah baris ketiga baru untuk tabel pengujian kita, kali ini dengan nol di kolom Data:
INSERT dbo.Test (RowID, Data) VALUES (3, 0);
Seperti yang diharapkan, penyisipan ini berjalan tanpa pemblokiran, menghasilkan tabel yang terlihat seperti ini:
Ingat, sesi kedua masih berlangsung eksklusif kunci pada baris 1 dan 2 pada saat ini. Kami bebas mendapatkan kunci di baris 3 jika perlu. Kueri berikut adalah yang akan kita gunakan untuk menunjukkan perilaku dengan banyak referensi ke tabel target:
-- Multi-reference update test UPDATE WriteRef SET Data = ReadRef.Data * 2 OUTPUT ReadRef.RowID, ReadRef.Data, INSERTED.RowID AS UpdatedRowID, INSERTED.Data AS NewDataValue FROM dbo.Test AS ReadRef JOIN dbo.Test AS WriteRef ON WriteRef.RowID = ReadRef.RowID + 2 WHERE ReadRef.RowID = 1;
Ini adalah kueri yang lebih kompleks, tetapi pengoperasiannya relatif sederhana. Ada dua referensi ke tabel pengujian, yang satu saya alias sebagai ReadRef, dan yang lainnya sebagai WriteRef. Idenya adalah untuk membaca dari baris 1 (menggunakan versi baris) melalui ReadRef, dan untuk memperbarui baris ketiga (yang memerlukan kunci pembaruan) menggunakan WriteRef.
Kueri menentukan baris 1 secara eksplisit dalam klausa where untuk referensi tabel bacaan. Ini bergabung dengan referensi penulisan ke tabel yang sama dengan menambahkan 2 ke RowID itu (jadi identifikasi baris 3). Pernyataan pembaruan juga menggunakan klausa keluaran untuk mengembalikan kumpulan hasil yang menunjukkan nilai yang dibaca dari tabel sumber dan hasil perubahan yang dibuat pada baris 3.
Perkiraan rencana kueri untuk pernyataan ini adalah sebagai berikut:
Properti pencarian berlabel (1) tunjukkan bahwa pencarian ini ada di ReadRef alias, membaca data dari baris dengan RowID 1:
Operasi pencarian ini tidak menemukan baris yang akan diperbarui, jadi kunci pembaruan tidak diambil; pembacaan dilakukan menggunakan data berversi. Pembacaan tidak diblokir oleh kunci eksklusif yang dipegang oleh sesi lain.
Skalar komputasi berlabel (2) mendefinisikan ekspresi berlabel 1004 yang menghitung nilai kolom Data yang diperbarui. Ekspresi 1009 menghitung ID baris yang akan diperbarui (1 + 2 =ID baris 3):
Pencarian kedua adalah referensi ke tabel yang sama (3). Pencarian ini menempatkan baris yang akan diperbarui (baris 3) menggunakan ekspresi 1009:
Karena pencarian ini menempatkan baris yang akan diubah, kunci pembaruan diambil alih-alih menggunakan versi baris. Tidak ada kunci eksklusif yang bertentangan pada baris ID 3, sehingga permintaan kunci segera diberikan.
Operator terakhir yang disorot (4) adalah operasi pembaruan itu sendiri. Kunci pembaruan pada baris 3 ditingkatkan menjadi eksklusif kunci pada titik ini, tepat sebelum modifikasi benar-benar dilakukan. Operator ini juga mengembalikan data yang ditentukan dalam klausa keluaran dari pernyataan pembaruan:
Hasil dari pernyataan update (dihasilkan oleh klausa output) ditunjukkan di bawah ini:
Status akhir tabel adalah seperti yang ditunjukkan di bawah ini:
Kami dapat mengonfirmasi kunci yang diambil selama eksekusi menggunakan jejak Profiler:
Ini menunjukkan bahwa hanya satu pembaruan kunci baris kunci diperoleh. Saat baris ini mencapai operator pembaruan, kunci diubah menjadi eksklusif kunci. Di akhir pernyataan, kunci dilepaskan.
Anda mungkin dapat melihat dari keluaran pelacakan bahwa nilai hash kunci untuk baris yang dikunci pembaruan adalah (98ec012aa510) di database pengujian saya. Kueri berikut menunjukkan bahwa hash kunci ini memang terkait dengan RowID 3 dalam indeks berkerumun:
SELECT RowID, %%LockRes%% FROM dbo.Test;
Perhatikan bahwa kunci pembaruan yang diambil dalam contoh ini berumur lebih pendek daripada kunci pembaruan yang diambil jika kita menetapkan UPDLOCK
petunjuk. Kunci pembaruan internal ini dirilis di akhir pernyataan, sedangkan UPDLOCK
kunci ditahan sampai akhir transaksi.
Ini menyimpulkan demonstrasi kasus di mana RCSI memperoleh kunci pembaruan untuk membaca data berkomitmen saat ini alih-alih menggunakan versi baris.
Kunci Bersama dan Rentang Kunci di bawah RCSI
Ada sejumlah skenario lain di mana mesin database masih dapat memperoleh kunci di bawah RCSI. Semua situasi ini berkaitan dengan kebutuhan untuk mempertahankan kebenaran yang akan terancam dengan mengandalkan data berversi yang berpotensi kedaluwarsa.
Kunci Bersama diambil untuk Validasi Kunci Asing
Untuk dua tabel dalam hubungan kunci asing langsung, mesin database perlu mengambil langkah-langkah untuk memastikan batasan tidak dilanggar dengan mengandalkan pembacaan versi yang berpotensi basi. Implementasi saat ini melakukan ini dengan beralih ke mengunci baca berkomitmen saat mengakses data sebagai bagian dari pemeriksaan kunci asing otomatis.
Mengambil kunci bersama memastikan pemeriksaan integritas membaca data berkomitmen terbaru (bukan versi lama), atau memblokir karena modifikasi dalam penerbangan bersamaan. Peralihan ke penguncian baca yang dilakukan hanya berlaku untuk metode akses tertentu yang digunakan untuk memeriksa data kunci asing; akses data lain dalam pernyataan yang sama terus menggunakan versi baris.
Perilaku ini hanya berlaku untuk pernyataan yang mengubah data, di mana perubahan tersebut secara langsung memengaruhi hubungan kunci asing. Untuk modifikasi tabel yang direferensikan (induk), ini berarti pembaruan yang memengaruhi nilai yang direferensikan (kecuali jika disetel ke NULL
) dan semua penghapusan. Untuk tabel referensi (anak), ini berarti semua sisipan dan pembaruan (sekali lagi, kecuali referensi kuncinya adalah NULL
). Pertimbangan yang sama berlaku untuk efek komponen dari MERGE
.
Contoh rencana eksekusi yang menunjukkan pencarian kunci asing yang menggunakan kunci bersama ditunjukkan di bawah ini:
Serializable untuk cascading foreign key
Di mana hubungan kunci asing memiliki tindakan cascading, kebenaran memerlukan eskalasi lokal ke semantik isolasi serial. Ini berarti Anda akan melihat kunci rentang kunci diambil untuk tindakan referensial berjenjang. Seperti halnya kunci pembaruan yang terlihat sebelumnya, kunci rentang kunci ini dicakupkan ke pernyataan, bukan transaksi. Contoh rencana eksekusi yang menunjukkan di mana kunci serialisasi internal diambil di bawah RCSI ditunjukkan di bawah ini:
Skenario lain
Ada banyak kasus khusus lainnya di mana mesin secara otomatis memperpanjang masa pakai kunci, atau secara lokal meningkatkan tingkat isolasi untuk memastikan kebenarannya. Ini termasuk semantik serial yang digunakan saat mempertahankan tampilan terindeks terkait, atau saat mempertahankan indeks yang memiliki IGNORE_DUP_KEY
kumpulan opsi.
Pesan yang dapat diambil adalah bahwa RCSI mengurangi jumlah penguncian, tetapi tidak selalu dapat menghilangkannya sepenuhnya.
Lain kali
Postingan berikutnya dalam seri ini membahas tingkat isolasi snapshot.
[ Lihat indeks untuk keseluruhan seri ]