SQL Server menyediakan dua implementasi fisik dari read commited tingkat isolasi yang ditentukan oleh standar SQL, mengunci read commit dan read commited snapshot isolasi (RCSI ). Sementara kedua implementasi memenuhi persyaratan yang ditetapkan dalam standar SQL untuk perilaku isolasi read-commited, RCSI memiliki perilaku fisik yang sangat berbeda dari implementasi penguncian yang kita lihat di postingan sebelumnya dalam seri ini.
Jaminan Logis
Standar SQL mengharuskan transaksi yang beroperasi pada tingkat isolasi berkomitmen baca tidak mengalami pembacaan kotor. Cara lain untuk menyatakan persyaratan ini adalah dengan mengatakan transaksi yang telah dibaca hanya boleh menemukan data yang dikomit .
Standar juga mengatakan bahwa transaksi yang dilakukan membaca mungkin mengalami fenomena konkurensi yang dikenal sebagai pembacaan dan phantom yang tidak dapat diulang (meskipun sebenarnya tidak diharuskan untuk melakukannya). Seperti yang terjadi, kedua implementasi fisik dari isolasi read-commit di SQL Server dapat mengalami pembacaan yang tidak dapat diulang dan baris phantom, meskipun detail tepatnya sangat berbeda.
Tampilan data yang dikomit secara tepat waktu
Jika opsi basis data READ_COMMITTED_SNAPSHOT
di ON
, SQL Server menggunakan implementasi versi baris dari tingkat isolasi berkomitmen baca. Saat ini diaktifkan, transaksi yang meminta isolasi baca berkomitmen secara otomatis menggunakan implementasi RCSI; tidak ada perubahan pada kode T-SQL yang ada diperlukan untuk menggunakan RCSI. Perhatikan baik-baik bahwa ini tidak sama dengan mengatakan bahwa kode akan berperilaku sama di bawah RCSI seperti saat menggunakan implementasi penguncian dari read commit, sebenarnya hal ini secara umum tidak demikian .
Tidak ada standar SQL yang mengharuskan data yang dibaca oleh transaksi yang telah dibaca menjadi terbaru data yang berkomitmen. Implementasi SQL Server RCSI memanfaatkan ini untuk menyediakan transaksi dengan tampilan point-in-time data berkomitmen, di mana titik waktu itu adalah saat pernyataan saat ini dimulai eksekusi (bukan saat transaksi yang mengandung dimulai).
Ini sangat berbeda dari perilaku implementasi penguncian SQL Server dari read commit, di mana pernyataan melihat data yang paling baru di-commit pada saat setiap item dibaca secara fisik . Mengunci rilis yang telah dibaca akan dikunci bersama secepat mungkin, sehingga kumpulan data yang ditemukan mungkin berasal dari titik waktu yang sangat berbeda.
Untuk meringkas, mengunci baca berkomitmen melihat setiap baris seperti pada saat itu dikunci sebentar dan dibaca secara fisik; RCSI melihat semua baris sebagaimana mereka pada saat pernyataan itu dimulai. Kedua implementasi dijamin tidak akan pernah melihat data yang tidak dikomit, tetapi data yang mereka temui bisa sangat berbeda.
Implikasi dari tampilan point-in-time
Melihat view point-in-time dari data yang dikomit mungkin tampak lebih unggul daripada perilaku implementasi penguncian yang lebih kompleks. Jelas, misalnya, bahwa tampilan point-in-time tidak dapat mengalami masalah baris yang hilang atau menemukan baris yang sama beberapa kali , yang keduanya dimungkinkan di bawah penguncian baca yang dilakukan isolasi.
Keuntungan penting kedua dari RCSI adalah tidak memperoleh kunci bersama saat membaca data, karena data berasal dari versi baris store daripada diakses secara langsung. Kurangnya kunci bersama dapat secara dramatis meningkatkan konkurensi dengan menghilangkan konflik dengan transaksi bersamaan yang ingin mendapatkan kunci yang tidak kompatibel. Keuntungan ini biasanya diringkas dengan mengatakan bahwa pembaca tidak memblokir penulis di bawah RCSI, dan sebaliknya. Sebagai konsekuensi lebih lanjut dari pengurangan pemblokiran karena permintaan kunci yang tidak kompatibel, peluang untuk kebuntuan biasanya sangat berkurang saat dijalankan di bawah RCSI.
Namun, manfaat ini tidak datang tanpa biaya dan peringatan . Untuk satu hal, mempertahankan versi baris yang dikomit menghabiskan sumber daya sistem, jadi penting bahwa lingkungan fisik dikonfigurasi untuk mengatasi hal ini, terutama dalam hal tempdb persyaratan kinerja dan memori/ruang disk.
Peringatan kedua sedikit lebih halus:RCSI memberikan tampilan snapshot dari data yang dikomit sebagaimana adanya di awal pernyataan, tetapi tidak ada yang mencegah data nyata diubah (dan perubahan itu dilakukan) saat pernyataan RCSI dijalankan. Tidak ada kunci bersama, ingat. Konsekuensi langsung dari poin kedua ini adalah bahwa kode T-SQL yang berjalan di bawah RCSI mungkin membuat keputusan berdasarkan informasi yang kedaluwarsa , dibandingkan dengan status komitmen database saat ini. Kami akan berbicara lebih banyak tentang ini segera.
Ada satu pengamatan terakhir (khusus implementasi) yang ingin saya buat tentang RCSI sebelum kita melanjutkan. Fungsi skalar dan multi-pernyataan mengeksekusi menggunakan konteks T-SQL internal yang berbeda dari pernyataan yang berisi. Ini berarti bahwa tampilan point-in-time yang terlihat di dalam pemanggilan fungsi skalar atau multi-pernyataan dapat lebih lambat dari tampilan point-in-time yang dilihat oleh sisa pernyataan. Hal ini dapat mengakibatkan inkonsistensi yang tidak terduga, karena bagian yang berbeda dari pernyataan yang sama melihat data dari titik waktu yang berbeda . Perilaku aneh dan membingungkan ini tidak berlaku untuk fungsi in-line, yang melihat snapshot yang sama dengan pernyataan yang menampilkannya.
Bacaan dan phantom yang tidak dapat diulang
Diberikan pandangan point-in-time tingkat pernyataan dari status komitmen database, mungkin tidak langsung terlihat bagaimana transaksi baca berkomitmen di bawah RCSI mungkin mengalami fenomena baris baca atau hantu yang tidak dapat diulang. Memang, jika kita membatasi pemikiran kita pada lingkup satu pernyataan , tidak satu pun dari fenomena ini yang mungkin terjadi di bawah RCSI.
Membaca data yang sama beberapa kali dalam pernyataan yang sama di bawah RCSI akan selalu mengembalikan nilai data yang sama, tidak ada data yang hilang di antara pembacaan tersebut, dan juga tidak ada data baru yang akan muncul. Jika Anda bertanya-tanya seperti apa pernyataan yang mungkin membaca data yang sama lebih dari sekali, pikirkan tentang kueri yang mereferensikan tabel yang sama lebih dari sekali, mungkin dalam sebuah subkueri.
Konsistensi pembacaan tingkat pernyataan merupakan konsekuensi nyata dari pembacaan yang dikeluarkan terhadap snapshot data yang tetap. Alasan RCSI tidak memberikan perlindungan dari pembacaan dan hantu yang tidak dapat diulang adalah bahwa fenomena standar SQL ini didefinisikan pada tingkat transaksi. Beberapa pernyataan dalam transaksi yang berjalan di RCSI mungkin melihat data yang berbeda, karena setiap pernyataan melihat tampilan titik waktu pada saat pernyataan tertentu dimulai.
Untuk meringkas, setiap pernyataan dalam transaksi RCSI melihat kumpulan data berkomitmen statis, tetapi kumpulan itu dapat berubah di antara pernyataan di dalam transaksi yang sama.
Data kedaluwarsa
Kemungkinan kode T-SQL kami membuat keputusan penting berdasarkan informasi kedaluwarsa lebih dari sedikit meresahkan. Pertimbangkan sejenak bahwa snapshot point-in-time yang digunakan oleh satu pernyataan yang berjalan di bawah RCSI mungkin tua secara sewenang-wenang .
Sebuah pernyataan yang berjalan untuk jangka waktu yang cukup lama akan terus melihat keadaan komitmen database seperti ketika pernyataan itu dimulai. Sementara itu, pernyataan tersebut tidak memiliki semua perubahan yang dilakukan yang terjadi dalam database sejak saat itu.
Ini bukan untuk mengatakan bahwa masalah yang terkait dengan mengakses data basi di bawah RCSI terbatas pada berjalan lama pernyataan, tetapi masalahnya mungkin lebih menonjol dalam kasus seperti itu.
Pertanyaan waktu
Masalah data kedaluwarsa ini berlaku untuk semua pernyataan RCSI pada prinsipnya, tidak peduli seberapa cepat mereka menyelesaikannya. Betapapun kecilnya jendela waktu, selalu ada kemungkinan bahwa operasi bersamaan dapat mengubah kumpulan data yang sedang kita kerjakan, tanpa kita menyadari perubahan itu. Mari kita lihat kembali salah satu contoh sederhana yang kita gunakan sebelumnya saat menjelajahi perilaku mengunci baca yang dilakukan:
INSERT dbo.OverdueInvoices SELECT I.InvoiceNumber FROM dbo.Invoices AS I WHERE I.TotalDue > ( SELECT SUM(P.Amount) FROM dbo.Payments AS P WHERE P.InvoiceNumber = I.InvoiceNumber );
Saat dijalankan di bawah RCSI, pernyataan ini tidak bisa lihat modifikasi basis data yang dilakukan setelah pernyataan mulai dijalankan. Meskipun kami tidak akan menemukan masalah baris yang terlewat atau ditemukan banyak kali di bawah penerapan penguncian, transaksi bersamaan mungkin menambahkan pembayaran yang seharusnya untuk mencegah pelanggan dikirimi surat peringatan keras tentang pembayaran yang terlambat setelah pernyataan di atas mulai dijalankan.
Anda mungkin dapat memikirkan banyak masalah potensial lainnya yang mungkin terjadi dalam skenario ini, atau dalam skenario lain yang secara konseptual serupa. Semakin lama pernyataan berjalan, semakin usang tampilan databasenya, dan semakin besar cakupan konsekuensi yang mungkin tidak diinginkan.
Tentu saja, ada banyak faktor yang meringankan dalam contoh khusus ini. Perilaku itu mungkin terlihat sangat dapat diterima. Lagi pula, mengirim surat pengingat karena pembayaran terlambat beberapa detik adalah tindakan yang mudah dipertahankan. Namun prinsipnya tetap.
Kegagalan Aturan Bisnis dan Risiko Integritas
Masalah yang lebih serius dapat muncul dari penggunaan informasi yang ketinggalan zaman daripada mengirim surat peringatan beberapa detik lebih awal. Contoh yang baik dari kelas kelemahan ini dapat dilihat dengan kode pemicu digunakan untuk menegakkan aturan integritas yang mungkin terlalu rumit untuk diterapkan dengan batasan integritas referensial deklaratif. Sebagai ilustrasi, pertimbangkan kode berikut, yang menggunakan pemicu untuk menerapkan variasi batasan kunci asing, tetapi yang memberlakukan hubungan hanya untuk baris tabel turunan tertentu:
ALTER DATABASE Sandpit SET READ_COMMITTED_SNAPSHOT ON WITH ROLLBACK IMMEDIATE; GO SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO CREATE TABLE dbo.Parent (ParentID integer PRIMARY KEY); GO CREATE TABLE dbo.Child ( ChildID integer IDENTITY PRIMARY KEY, ParentID integer NOT NULL, CheckMe bit NOT NULL ); GO CREATE TRIGGER dbo.Child_AI ON dbo.Child AFTER INSERT AS BEGIN -- Child rows with CheckMe = true -- must have an associated parent row IF EXISTS ( SELECT ins.ParentID FROM inserted AS ins WHERE ins.CheckMe = 1 EXCEPT SELECT P.ParentID FROM dbo.Parent AS P ) BEGIN RAISERROR ('Integrity violation!', 16, 1); ROLLBACK TRANSACTION; END END; GO -- Insert parent row #1 INSERT dbo.Parent (ParentID) VALUES (1);
Sekarang pertimbangkan transaksi yang berjalan di sesi lain (gunakan jendela SSMS lain untuk ini jika Anda mengikuti) yang menghapus baris induk #1, tetapi belum melakukan:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED; BEGIN TRANSACTION; DELETE FROM dbo.Parent WHERE ParentID = 1;
Kembali ke sesi awal, kami mencoba menyisipkan baris anak (yang dicentang) yang mereferensikan induk ini:
INSERT dbo.Child (ParentID, CheckMe) VALUES (1, 1);
Kode pemicu dijalankan, tetapi karena RCSI hanya melihat komit data pada saat pernyataan dimulai, ia masih melihat baris induk (bukan penghapusan yang tidak dikomit) dan penyisipan berhasil !
Transaksi yang menghapus baris induk sekarang dapat melakukan perubahan dengan sukses, meninggalkan database dalam tidak konsisten menyatakan dalam hal logika pemicu kami:
COMMIT TRANSACTION; SELECT P.* FROM dbo.Parent AS P; SELECT C.* FROM dbo.Child AS C;
Ini adalah contoh yang disederhanakan tentu saja, dan yang dapat dengan mudah dielakkan menggunakan fasilitas kendala bawaan. Aturan bisnis yang jauh lebih kompleks dan batasan integritas semu dapat ditulis di dalam dan di luar pemicu . Potensi perilaku yang salah di bawah RCSI harus jelas.
Perilaku pemblokiran dan data komitmen terbaru
Saya sebutkan sebelumnya bahwa kode T-SQL tidak dijamin untuk berperilaku dengan cara yang sama di bawah RCSI membaca berkomitmen seperti yang dilakukan dengan penerapan penguncian. Contoh kode pemicu sebelumnya adalah ilustrasi yang baik tentang hal itu, tetapi saya perlu menekankan bahwa masalah umum tidak terbatas pada pemicu .
RCSI biasanya bukan pilihan yang baik untuk kode T-SQL apa pun yang kebenarannya bergantung pada pemblokiran jika ada perubahan yang tidak dikomit secara bersamaan. RCSI mungkin juga bukan pilihan yang tepat jika kodenya bergantung pada pembacaan saat ini data yang dikomit, bukan data komit terbaru seperti pada saat pernyataan dimulai. Kedua pertimbangan ini terkait, tetapi keduanya bukanlah hal yang sama.
Mengunci pembacaan yang dilakukan di bawah RCSI
SQL Server menyediakan satu cara untuk meminta penguncian baca commit saat RCSI diaktifkan, menggunakan petunjuk tabel READCOMMITTEDLOCK
. Kami dapat memodifikasi pemicu kami untuk menghindari masalah yang ditunjukkan di atas dengan menambahkan petunjuk ini ke tabel yang memerlukan perilaku pemblokiran agar berfungsi dengan benar:
ALTER TRIGGER dbo.Child_AI ON dbo.Child AFTER INSERT AS BEGIN -- Child rows with CheckMe = true -- must have an associated parent row IF EXISTS ( SELECT ins.ParentID FROM inserted AS ins WHERE ins.CheckMe = 1 EXCEPT SELECT P.ParentID FROM dbo.Parent AS P WITH (READCOMMITTEDLOCK) -- NEW!! ) BEGIN RAISERROR ('Integrity violation!', 16, 1); ROLLBACK TRANSACTION; END END;
Dengan perubahan ini, upaya untuk memasukkan blok baris anak yang berpotensi menjadi yatim piatu hingga transaksi penghapusan dilakukan (atau dibatalkan). Jika penghapusan dilakukan, kode pemicu mendeteksi pelanggaran integritas dan memunculkan kesalahan yang diharapkan.
Mengidentifikasi kueri yang mungkin tidak bekerja dengan benar di bawah RCSI adalah tugas non-sepele yang mungkin memerlukan pengujian ekstensif untuk memperbaikinya (dan harap diingat bahwa masalah ini cukup umum dan tidak terbatas pada kode pemicu!) Selain itu, tambahkan READCOMMITTEDLOCK
petunjuk ke setiap tabel yang membutuhkannya bisa menjadi proses yang membosankan dan rawan kesalahan. Sampai SQL Server menyediakan opsi cakupan yang lebih luas untuk meminta implementasi penguncian jika diperlukan, kami terjebak dengan menggunakan petunjuk tabel.
Lain kali
Postingan berikutnya dalam seri ini melanjutkan pemeriksaan kami terhadap isolasi snapshot berkomitmen yang telah dibaca, dengan melihat perilaku mengejutkan dari pernyataan modifikasi data di bawah RCSI.
[ Lihat indeks untuk keseluruhan seri ]