Baca yang dilakukan adalah terlemah kedua dari empat tingkat isolasi yang ditentukan oleh standar SQL. Namun demikian, ini adalah tingkat isolasi default untuk banyak mesin database, termasuk SQL Server. Postingan ini dalam seri tentang tingkat isolasi dan properti ACID dari transaksi melihat jaminan logis dan fisik yang sebenarnya disediakan oleh isolasi read-commited.
Jaminan Logis
Standar SQL mengharuskan transaksi yang berjalan di bawah isolasi baca yang berkomitmen hanya membaca berkomitmen data. Ini mengungkapkan persyaratan ini dengan melarang fenomena konkurensi yang dikenal sebagai pembacaan kotor. Pembacaan kotor terjadi di mana transaksi membaca data yang telah ditulis oleh transaksi lain, sebelum transaksi kedua selesai. Cara lain untuk mengungkapkan hal ini adalah dengan mengatakan bahwa pembacaan kotor terjadi ketika transaksi membaca data yang tidak dikomit.
Standar tersebut juga menyebutkan bahwa transaksi yang berjalan pada isolasi baca yang dilakukan mungkin mengalami fenomena konkurensi yang dikenal sebagai pembacaan yang tidak dapat diulang dan hantu . Meskipun banyak buku menjelaskan fenomena ini dalam hal transaksi yang dapat melihat item data yang berubah atau baru jika data selanjutnya dibaca kembali, penjelasan ini dapat memperkuat kesalahpahaman bahwa fenomena konkurensi hanya dapat terjadi di dalam transaksi eksplisit yang berisi banyak pernyataan. Ini tidak begitu. Satu pernyataan tanpa transaksi eksplisit sama rentannya dengan fenomena baca dan hantu yang tidak dapat diulang, seperti yang akan kita lihat segera.
Cukup banyak yang dikatakan standar tentang masalah isolasi baca yang dilakukan. Pada pandangan pertama, hanya membaca data yang berkomitmen tampak seperti jaminan yang cukup baik untuk perilaku yang masuk akal, tetapi seperti biasa, iblis ada dalam detailnya. Segera setelah Anda mulai mencari celah yang potensial dalam definisi ini, menjadi terlalu mudah untuk menemukan contoh di mana transaksi komitmen baca kami mungkin tidak menghasilkan hasil yang kami harapkan. Sekali lagi, kita akan membahas ini secara lebih rinci dalam satu atau dua saat.
Implementasi Fisik yang Berbeda
Setidaknya ada dua hal yang berarti bahwa perilaku yang diamati dari tingkat isolasi berkomitmen baca mungkin sangat berbeda pada mesin basis data yang berbeda. Pertama, persyaratan standar SQL untuk hanya membaca data yang dikomit tidak berarti bahwa data berkomitmen yang dibaca oleh suatu transaksi akan menjadi terbaru data yang dikomit.
Mesin basis data diizinkan untuk membaca versi baris yang dikomit dari titik mana pun di masa lalu , dan masih mematuhi definisi standar SQL. Beberapa produk database populer menerapkan isolasi baca berkomitmen dengan cara ini. Hasil kueri yang diperoleh di bawah implementasi isolasi read-commit ini mungkin secara sewenang-wenang kedaluwarsa , jika dibandingkan dengan status komitmen database saat ini. Kami akan membahas topik ini karena berlaku untuk SQL Server di posting berikutnya dalam seri ini.
Hal kedua yang ingin saya tarik perhatian Anda adalah bahwa definisi standar SQL tidak mencegah implementasi tertentu dari memberikan perlindungan efek konkurensi tambahan selain mencegah pembacaan kotor . Standar hanya menetapkan bahwa pembacaan kotor tidak diperbolehkan, tidak mengharuskan fenomena konkurensi lainnya harus diizinkan pada tingkat isolasi tertentu.
Untuk memperjelas poin kedua ini, mesin database yang sesuai standar dapat mengimplementasikan semua level isolasi menggunakan serializable perilaku jika demikian memilih. Beberapa mesin basis data komersial utama juga menyediakan implementasi komitmen baca yang lebih dari sekadar mencegah pembacaan kotor (meskipun tidak ada yang memberikan Isolasi lengkap dalam ACID arti kata).
Selain itu, untuk beberapa produk populer, baca komitmen isolasi adalah terendah tingkat isolasi tersedia; implementasi mereka dari read uncommitted isolasi persis sama dengan read commit. Ini diizinkan oleh standar, tetapi perbedaan semacam ini menambah kerumitan pada tugas yang sudah sulit untuk memigrasi kode dari satu platform ke platform lainnya. Saat berbicara tentang perilaku tingkat isolasi, biasanya penting untuk menentukan platform tertentu juga.
Sejauh yang saya tahu, SQL Server unik di antara mesin database komersial utama dalam menyediakan dua implementasi tingkat isolasi read-commited, masing-masing dengan perilaku fisik yang sangat berbeda. Pos ini mencakup yang pertama, mengunci baca berkomitmen.
Penguncian SQL Server Baca Berkomitmen
Jika opsi basis data READ_COMMITTED_SNAPSHOT
adalah OFF
, SQL Server menggunakan penguncian implementasi tingkat isolasi baca berkomitmen, di mana kunci bersama diambil untuk mencegah transaksi bersamaan dari memodifikasi data secara bersamaan, karena modifikasi akan memerlukan kunci eksklusif, yang tidak kompatibel dengan kunci bersama.
Perbedaan utama antara SQL Server mengunci baca berkomitmen dan mengunci baca berulang (yang juga mengambil kunci bersama saat membaca data) adalah bahwa baca berkomitmen melepaskan kunci bersama sesegera mungkin , sedangkan pembacaan berulang menahan kunci ini hingga akhir transaksi terlampir.
Saat mengunci baca yang berkomitmen memperoleh kunci pada perincian baris, kunci bersama yang diambil pada satu baris dilepaskan saat kunci bersama diambil di baris berikutnya . Pada perincian halaman, kunci halaman bersama dilepaskan saat baris pertama di halaman berikutnya dibaca, dan seterusnya. Kecuali jika petunjuk perincian kunci disertakan dengan kueri, mesin basis data memutuskan tingkat perincian untuk memulai. Perhatikan bahwa petunjuk perincian hanya diperlakukan sebagai saran oleh mesin, kunci yang kurang terperinci dari yang diminta mungkin masih diambil pada awalnya. Penguncian juga dapat ditingkatkan selama eksekusi dari tingkat baris atau halaman ke tingkat partisi atau tabel tergantung pada konfigurasi sistem.
Poin penting di sini adalah bahwa kunci bersama biasanya disimpan hanya untuk waktu yang sangat singkat selama pernyataan dijalankan. Untuk mengatasi satu kesalahpahaman umum secara eksplisit, mengunci baca yang dilakukan tidak tahan kunci bersama sampai akhir pernyataan.
Mengunci Perilaku Baca yang Dikomit
Kunci bersama jangka pendek yang digunakan oleh implementasi SQL Server locking read commited memberikan sangat sedikit jaminan yang biasanya diharapkan dari transaksi database oleh programmer T-SQL. Secara khusus, pernyataan yang berjalan di bawah penguncian baca berkomitmen isolasi:
- Dapat menemukan baris yang sama beberapa kali;
- Dapat melewatkan beberapa baris sama sekali; dan
- Apakah tidak memberikan tampilan tepat waktu dari data
Daftar itu mungkin tampak lebih seperti deskripsi perilaku aneh yang mungkin lebih Anda kaitkan dengan penggunaan NOLOCK
petunjuk, tetapi semua hal ini benar-benar dapat terjadi, dan memang terjadi saat menggunakan penguncian baca yang dilakukan dengan isolasi.
Contoh
Pertimbangkan tugas sederhana menghitung baris dalam tabel, menggunakan kueri pernyataan tunggal yang jelas. Di bawah penguncian baca, isolasi berkomitmen dengan perincian penguncian baris, kueri kami akan mengambil kunci bersama di baris pertama, membacanya, melepaskan kunci bersama, pindah ke baris berikutnya, dan seterusnya hingga mencapai akhir strukturnya. sedang membaca. Demi contoh ini, asumsikan kueri kita membaca indeks b-tree dalam urutan kunci menaik (meskipun bisa juga menggunakan urutan menurun, atau strategi lainnya).
Karena hanya satu baris dikunci bersama pada saat tertentu dalam waktu tertentu, sangat mungkin bagi transaksi bersamaan untuk mengubah baris yang tidak terkunci dalam indeks yang dilintasi kueri kami. Jika modifikasi bersamaan ini mengubah nilai kunci indeks, mereka akan menyebabkan baris bergerak di dalam struktur indeks. Dengan mempertimbangkan kemungkinan tersebut, diagram di bawah ini menggambarkan dua skenario bermasalah yang dapat terjadi:
Panah paling atas menunjukkan baris yang telah kita hitung memiliki kunci indeks yang dimodifikasi secara bersamaan sehingga baris bergerak di depan posisi pemindaian saat ini dalam indeks, yang berarti baris tersebut akan dihitung dua kali . Panah kedua menunjukkan baris yang belum ditemukan pemindaian kami bergerak di belakang posisi pemindaian, artinya baris tersebut tidak akan dihitung sama sekali.
Bukan pemandangan tepat waktu
Bagian sebelumnya menunjukkan bagaimana mengunci baca yang dilakukan dapat kehilangan data sepenuhnya, atau menghitung item yang sama beberapa kali (lebih dari dua kali, jika kita tidak beruntung). Butir ketiga dalam daftar perilaku tak terduga menyatakan bahwa mengunci baca yang dilakukan juga tidak memberikan tampilan data secara tepat waktu.
Alasan di balik pernyataan itu sekarang seharusnya mudah dilihat. Kueri penghitungan kami, misalnya, dapat dengan mudah membaca data yang dimasukkan oleh transaksi bersamaan setelah kueri kami mulai dijalankan. Demikian pula, data yang dilihat kueri kami mungkin dimodifikasi oleh aktivitas bersamaan setelah kueri kami dimulai dan sebelum selesai. Terakhir, data yang telah kami baca dan hitung mungkin dihapus oleh transaksi bersamaan sebelum kueri kami selesai.
Jelas, data yang dilihat oleh pernyataan atau transaksi yang berjalan di bawah penguncian baca, isolasi berkomitmen sesuai dengan tidak ada satu pun status database pada titik waktu tertentu . Data yang kami temui mungkin berasal dari berbagai titik waktu yang berbeda, dengan satu-satunya faktor umum adalah bahwa setiap item mewakili nilai komitmen terbaru dari data tersebut pada saat dibaca (meskipun mungkin telah berubah atau menghilang sejak itu).
Seberapa serius masalah ini?
Ini semua mungkin tampak seperti keadaan yang cukup rumit jika Anda terbiasa memikirkan kueri pernyataan tunggal dan transaksi eksplisit Anda sebagai dieksekusi secara logis secara instan, atau berjalan melawan satu kondisi titik waktu komitmen tunggal dari database saat menggunakan tingkat isolasi SQL Server default. Ini tentu tidak cocok dengan konsep isolasi dalam arti ACID.
Mengingat kelemahan nyata dari jaminan yang diberikan dengan mengunci isolasi baca yang dilakukan, Anda mungkin mulai bertanya-tanya bagaimana ada kode T-SQL produksi Anda pernah bekerja dengan baik! Tentu saja, kami dapat menerima bahwa menggunakan tingkat isolasi di bawah serializable berarti kami melepaskan isolasi transaksi ACID penuh dengan imbalan potensi manfaat lainnya, tetapi seberapa seriuskah masalah ini dalam praktiknya?
Baris tidak ada dan dihitung ganda
Dua masalah pertama ini pada dasarnya bergantung pada aktivitas bersamaan yang mengubah kunci dalam struktur indeks yang sedang kami pindai. Perhatikan bahwa memindai di sini termasuk bagian pemindaian rentang parsial dari pencarian indeks , serta pemindaian indeks atau tabel tak terbatas yang sudah dikenal.
Jika kita (rentang) memindai struktur indeks yang kuncinya biasanya tidak dimodifikasi oleh aktivitas bersamaan, dua masalah pertama ini seharusnya tidak menjadi masalah praktis. Sulit untuk memastikan hal ini, karena rencana kueri dapat berubah untuk menggunakan metode akses yang berbeda, dan indeks yang baru dicari mungkin menyertakan kunci yang mudah menguap.
Kami juga harus mengingat bahwa banyak kueri produksi hanya benar-benar membutuhkan perkiraan atau jawaban upaya terbaik untuk beberapa jenis pertanyaan. Fakta bahwa beberapa baris hilang atau dihitung ganda mungkin tidak terlalu menjadi masalah dalam skema yang lebih luas. Pada sistem dengan banyak perubahan serentak, bahkan mungkin sulit untuk memastikan bahwa hasilnya adalah tidak akurat, mengingat datanya sering berubah. Dalam situasi seperti itu, jawaban yang kurang lebih benar mungkin cukup baik untuk keperluan konsumen data.
Tidak ada tampilan tepat waktu
Isu ketiga (pertanyaan tentang apa yang disebut pandangan titik-dalam-waktu 'konsisten' dari data) juga bermuara pada pertimbangan yang sama. Untuk tujuan pelaporan, di mana inkonsistensi cenderung menghasilkan pertanyaan canggung dari konsumen data, tampilan snapshot seringkali lebih disukai. Dalam kasus lain, jenis inkonsistensi yang timbul dari kurangnya pandangan data yang tepat waktu mungkin dapat ditoleransi.
Skenario bermasalah
Ada juga banyak kasus di mana kekhawatiran yang terdaftar akan menjadi penting. Misalnya, jika Anda menulis kode yang menerapkan aturan bisnis di T-SQL, Anda harus berhati-hati untuk memilih tingkat isolasi (atau mengambil tindakan lain yang sesuai) untuk menjamin kebenaran. Banyak aturan bisnis dapat ditegakkan menggunakan kunci asing atau batasan, di mana seluk-beluk pemilihan tingkat isolasi ditangani secara otomatis untuk Anda oleh mesin database. Sebagai aturan umum, menggunakan set bawaan integritas deklaratif fitur lebih disukai daripada membuat aturan Anda sendiri di T-SQL.
Ada kelas kueri lain yang tidak cukup menerapkan aturan bisnis per se , tetapi yang bagaimanapun mungkin memiliki konsekuensi yang tidak menguntungkan ketika dijalankan pada penguncian default membaca tingkat isolasi berkomitmen. Skenario ini tidak selalu sejelas contoh transfer uang antar rekening bank yang sering dikutip, atau memastikan bahwa saldo di sejumlah akun yang terhubung tidak pernah turun di bawah nol. Misalnya, pertimbangkan kueri berikut yang mengidentifikasi faktur yang telah jatuh tempo sebagai masukan untuk beberapa proses yang mengirimkan surat pengingat dengan kata-kata yang tegas:
INSERT dbo.OverdueInvoices SELECT I.InvoiceNumber FROM dbo.Invoices AS INV WHERE INV.TotalDue > ( SELECT SUM(P.Amount) FROM dbo.Payments AS P WHERE P.InvoiceNumber = I.InvoiceNumber );
Jelas kami tidak ingin mengirim surat kepada seseorang yang telah membayar penuh faktur mereka secara mencicil, hanya karena aktivitas basis data bersamaan pada saat kueri kami dijalankan berarti kami menghitung jumlah yang salah dari pembayaran yang diterima. Permintaan nyata pada sistem produksi nyata seringkali jauh lebih kompleks daripada contoh sederhana di atas, tentu saja.
Untuk menyelesaikan untuk hari ini, lihat kueri berikut dan lihat apakah Anda dapat melihat berapa banyak peluang yang ada untuk sesuatu yang tidak diinginkan terjadi, jika beberapa kueri tersebut dijalankan secara bersamaan pada tingkat isolasi baca berkomitmen penguncian (mungkin saat transaksi lain yang tidak terkait juga memodifikasi tabel Kasus):
-- Allocate the oldest unallocated case ID to -- the current case worker, while ensuring -- the worker never has more than three -- active cases at once. UPDATE dbo.Cases SET WorkerID = @WorkerID WHERE CaseID = ( -- Find the oldest unallocated case ID SELECT TOP (1) C2.CaseID FROM dbo.Cases AS C2 WHERE C2.WorkerID IS NULL ORDER BY C2.DateCreated DESC ) AND ( SELECT COUNT_BIG(*) FROM dbo.Cases AS C3 WHERE C3.WorkerID = @WorkerID ) < 3;
Setelah Anda mulai mencari semua kemungkinan kecil kesalahan kueri pada tingkat isolasi ini, akan sulit untuk menghentikannya. Ingatlah peringatan yang disebutkan sebelumnya seputar kebutuhan nyata untuk hasil yang benar-benar terisolasi dan akurat tepat waktu. Tidak apa-apa untuk memiliki kueri yang memberikan hasil yang cukup baik, selama Anda menyadari pengorbanan yang Anda buat dengan menggunakan komitmen baca.
Lain kali
Bagian selanjutnya dalam seri ini melihat implementasi fisik kedua dari isolasi read commit yang tersedia di SQL Server, isolasi snapshot read commit.
[ Lihat indeks untuk keseluruhan seri ]