Masalah konkurensi sulit dengan cara yang sama seperti pemrograman multi-utas. Kecuali jika isolasi serializable digunakan, akan sulit untuk membuat kode transaksi T-SQL yang akan selalu berfungsi dengan benar ketika pengguna lain membuat perubahan pada database pada saat yang sama.
Masalah potensial dapat menjadi tidak sepele bahkan jika 'transaksi' yang dimaksud adalah SELECT
tunggal sederhana penyataan. Untuk transaksi multi-pernyataan kompleks yang membaca dan menulis data, potensi hasil dan kesalahan yang tidak terduga dalam konkurensi tinggi dapat dengan cepat menjadi luar biasa. Mencoba menyelesaikan masalah konkurensi yang halus dan sulit direproduksi dengan menerapkan petunjuk penguncian acak atau metode coba-coba lainnya dapat menjadi pengalaman yang sangat membuat frustrasi.
Dalam banyak hal, tingkat isolasi snapshot tampak seperti solusi sempurna untuk masalah konkurensi ini. Ide dasarnya adalah bahwa setiap transaksi snapshot berperilaku seolah-olah dieksekusi terhadap salinan pribadinya sendiri dari status komitmen database, yang diambil pada saat transaksi dimulai. Menyediakan seluruh transaksi dengan tampilan data yang berkomitmen yang tidak berubah jelas menjamin hasil yang konsisten untuk operasi hanya-baca, tetapi bagaimana dengan transaksi yang mengubah data?
Isolasi snapshot menangani perubahan data secara optimis, dengan asumsi bahwa konflik antara penulis bersamaan akan relatif jarang terjadi. Di mana konflik tulis benar-benar terjadi, committer pertama menang dan transaksi yang kalah memiliki perubahan yang dibatalkan. Sangat disayangkan untuk transaksi yang dibatalkan, tentu saja, tetapi jika ini cukup jarang terjadi, manfaat isolasi snapshot dapat dengan mudah melebihi biaya kegagalan dan coba lagi sesekali.
Semantik isolasi snapshot yang relatif sederhana dan bersih (bila dibandingkan dengan alternatifnya) dapat menjadi keuntungan yang signifikan, terutama bagi orang yang tidak bekerja secara eksklusif di dunia database dan oleh karena itu tidak mengetahui berbagai tingkat isolasi dengan baik. Bahkan untuk profesional database berpengalaman, tingkat isolasi yang relatif 'intuitif' dapat melegakan.
Tentu saja, hal-hal jarang sesederhana pertama kali muncul, dan isolasi snapshot tidak terkecuali. Dokumentasi resmi melakukan pekerjaan yang cukup baik untuk menjelaskan keuntungan dan kerugian utama dari isolasi snapshot, sehingga sebagian besar artikel ini berkonsentrasi pada menjelajahi beberapa masalah yang kurang terkenal dan mengejutkan yang mungkin Anda temui. Namun, pertama-tama, lihat sekilas properti logis dari level isolasi ini:
Properti ACID dan Isolasi Snapshot
Isolasi snapshot bukan salah satu level isolasi yang ditentukan dalam SQL Standard, tetapi masih sering dibandingkan menggunakan 'fenomena konkurensi' yang didefinisikan di sana. Misalnya, tabel perbandingan berikut ini direproduksi dari Artikel Teknis SQL Server, "Isolasi Transaksi Berbasis Versi SQL Server 2005 Baris" oleh Kimberly L. Tripp dan Neal Graves:
Dengan memberikan tampilan tepat waktu dari data yang dikomit , isolasi snapshot memberikan perlindungan terhadap ketiga fenomena konkurensi yang ditampilkan di sana. Pembacaan kotor dicegah karena hanya data berkomitmen yang terlihat, dan sifat statis dari snapshot mencegah pembacaan yang tidak dapat diulang dan phantom untuk ditemui.
Namun, perbandingan ini (dan bagian yang disorot khususnya) hanya menunjukkan bahwa snapshot dan tingkat isolasi serial mencegah tiga fenomena spesifik yang sama. Itu tidak berarti mereka setara dalam segala hal. Yang penting, standar SQL-92 tidak mendefinisikan isolasi serial dalam hal tiga fenomena saja. Bagian 4.28 dari standar memberikan definisi lengkap:
Eksekusi transaksi SQL bersamaan pada tingkat isolasi SERIALIZABLE dijamin serializable. Eksekusi serializable didefinisikan sebagai eksekusi operasi dari transaksi SQL yang dieksekusi secara bersamaan yang menghasilkan efek yang sama seperti beberapa eksekusi serial dari transaksi SQL yang sama. Eksekusi serial adalah eksekusi di mana setiap transaksi SQL dijalankan hingga selesai sebelum transaksi SQL berikutnya dimulai.
Luas dan pentingnya jaminan tersirat di sini sering terlewatkan. Untuk menyatakannya dalam bahasa sederhana:
Setiap transaksi serial yang dijalankan dengan benar saat dijalankan sendiri akan terus dijalankan dengan benar dengan kombinasi transaksi bersamaan, atau akan dibatalkan dengan pesan kesalahan (biasanya kebuntuan dalam implementasi SQL Server).
Tingkat isolasi yang tidak dapat diserialisasi, termasuk isolasi snapshot, tidak memberikan jaminan kebenaran yang sama kuatnya.
Data Lama
Isolasi snapshot tampaknya sangat sederhana. Pembacaan selalu berasal dari data yang dikomit pada satu titik waktu, dan konflik penulisan secara otomatis terdeteksi dan ditangani. Bagaimana ini bukan solusi sempurna untuk semua kesulitan terkait konkurensi?
Salah satu masalah potensial adalah bahwa pembacaan snapshot tidak selalu mencerminkan status komitmen database saat ini. Transaksi snapshot sepenuhnya mengabaikan perubahan yang dilakukan oleh transaksi bersamaan lainnya setelah transaksi snapshot dimulai. Cara lain untuk mengatakannya adalah dengan mengatakan bahwa transaksi snapshot melihat data yang basi dan ketinggalan zaman. Meskipun perilaku ini mungkin persis seperti yang diperlukan untuk menghasilkan laporan tepat waktu yang akurat, perilaku ini mungkin tidak begitu sesuai dalam keadaan lain (misalnya, saat digunakan untuk menegakkan aturan di pemicu).
Tulis Miring
Isolasi snapshot juga rentan terhadap fenomena yang agak terkait yang dikenal sebagai write skew. Membaca data basi berperan dalam hal ini, tetapi masalah ini juga membantu memperjelas apa yang dilakukan dan tidak dilakukan snapshot 'pendeteksian konflik tulis'.
Write skew terjadi ketika dua transaksi bersamaan masing-masing membaca data yang dimodifikasi oleh transaksi lainnya. Tidak ada konflik penulisan yang terjadi karena kedua transaksi memodifikasi baris yang berbeda. Tidak ada transaksi yang melihat perubahan yang dibuat oleh yang lain, karena keduanya membaca dari satu titik waktu sebelum perubahan itu dilakukan.
Contoh klasik penulisan miring adalah masalah marmer putih dan hitam, tetapi saya ingin menunjukkan contoh sederhana lainnya di sini:
-- Create two empty tables CREATE TABLE A (x integer NOT NULL); CREATE TABLE B (x integer NOT NULL); -- Connection 1 SET TRANSACTION ISOLATION LEVEL SNAPSHOT; BEGIN TRANSACTION; INSERT A (x) SELECT COUNT_BIG(*) FROM B; -- Connection 2 SET TRANSACTION ISOLATION LEVEL SNAPSHOT; BEGIN TRANSACTION; INSERT B (x) SELECT COUNT_BIG(*) FROM A; COMMIT TRANSACTION; -- Connection 1 COMMIT TRANSACTION;
Di bawah isolasi snapshot, kedua tabel dalam skrip itu berakhir dengan satu baris yang berisi nilai nol. Ini adalah hasil yang benar, tetapi bukan hasil yang dapat diserialisasi:tidak sesuai dengan urutan eksekusi transaksi serial yang mungkin. Dalam jadwal yang benar-benar serial, satu transaksi harus diselesaikan sebelum yang lain dimulai, sehingga transaksi kedua akan menghitung baris yang dimasukkan oleh yang pertama. Ini mungkin terdengar seperti masalah teknis, tetapi ingat jaminan serializable yang kuat hanya berlaku jika transaksi benar-benar dapat serializable.
Kehalusan Deteksi Konflik
Konflik penulisan snapshot terjadi setiap kali transaksi snapshot mencoba mengubah baris yang telah dimodifikasi oleh transaksi lain yang dilakukan setelah transaksi snapshot dimulai. Ada dua kehalusan di sini:
- Transaksi sebenarnya tidak harus berubah nilai data apa pun; dan
- Transaksi tidak perlu mengubah kolom umum .
Skrip berikut menunjukkan kedua poin:
-- Test table CREATE TABLE dbo.Conflict ( ID1 integer UNIQUE, Value1 integer NOT NULL, ID2 integer UNIQUE, Value2 integer NOT NULL ); -- Insert one row INSERT dbo.Conflict (ID1, ID2, Value1, Value2) VALUES (1, 1, 1, 1); -- Connection 1 BEGIN TRANSACTION; UPDATE dbo.Conflict SET Value1 = 1 WHERE ID1 = 1; -- Connection 2 SET TRANSACTION ISOLATION LEVEL SNAPSHOT; BEGIN TRANSACTION; UPDATE dbo.Conflict SET Value2 = 1 WHERE ID2 = 1; -- Connection 1 COMMIT TRANSACTION;
Perhatikan hal berikut:
- Setiap transaksi menempatkan baris yang sama menggunakan indeks yang berbeda
- Tidak ada pembaruan yang mengakibatkan perubahan pada data yang sudah disimpan
- Dua transaksi 'memperbarui' kolom yang berbeda dalam satu baris.
Terlepas dari semua itu, ketika transaksi pertama dilakukan, transaksi kedua berakhir dengan kesalahan konflik pembaruan:
Ringkasan:Deteksi konflik selalu beroperasi pada tingkat seluruh baris, dan 'pembaruan' tidak harus benar-benar mengubah data apa pun. (Jika Anda bertanya-tanya, perubahan pada data LOB atau SLOB di luar baris juga dihitung sebagai perubahan pada baris untuk tujuan deteksi konflik).
Masalah Kunci Asing
Deteksi konflik juga berlaku untuk baris induk dalam hubungan kunci asing. Saat memodifikasi baris anak di bawah isolasi snapshot, perubahan ke baris induk di transaksi lain dapat memicu konflik. Seperti sebelumnya, logika ini berlaku untuk seluruh baris induk – pembaruan induk tidak harus memengaruhi kolom kunci asing itu sendiri. Setiap operasi pada tabel anak yang memerlukan pemeriksaan kunci asing otomatis dalam rencana eksekusi dapat mengakibatkan konflik yang tidak terduga.
Untuk mendemonstrasikannya, pertama buat tabel dan contoh data berikut:
CREATE TABLE dbo.Dummy ( x integer NULL ); CREATE TABLE dbo.Parent ( ParentID integer PRIMARY KEY, ParentValue integer NOT NULL ); CREATE TABLE dbo.Child ( ChildID integer PRIMARY KEY, ChildValue integer NOT NULL, ParentID integer NULL FOREIGN KEY REFERENCES dbo.Parent ); INSERT dbo.Parent (ParentID, ParentValue) VALUES (1, 1); INSERT dbo.Child (ChildID, ChildValue, ParentID) VALUES (1, 1, 1);
Sekarang jalankan yang berikut dari dua koneksi terpisah seperti yang ditunjukkan dalam komentar:
-- Connection 1 SET TRANSACTION ISOLATION LEVEL SNAPSHOT; BEGIN TRANSACTION; SELECT COUNT_BIG(*) FROM dbo.Dummy; -- Connection 2 (any isolation level) UPDATE dbo.Parent SET ParentValue = 1 WHERE ParentID = 1; -- Connection 1 UPDATE dbo.Child SET ParentID = NULL WHERE ChildID = 1; UPDATE dbo.Child SET ParentID = 1 WHERE ChildID = 1;
Pembacaan dari tabel dummy ada untuk memastikan transaksi snapshot telah resmi dimulai. Mengeluarkan BEGIN TRANSACTION
tidak cukup untuk melakukan ini; kita harus melakukan semacam akses data pada tabel pengguna.
Pembaruan pertama ke tabel Anak tidak menyebabkan konflik karena menyetel kolom referensi ke NULL
tidak memerlukan pemeriksaan tabel induk dalam rencana eksekusi (tidak ada yang perlu diperiksa). Pemroses kueri tidak menyentuh baris induk dalam rencana eksekusi, sehingga tidak ada konflik yang muncul.
Pembaruan kedua ke tabel Anak memicu konflik karena pemeriksaan kunci asing dilakukan secara otomatis. Saat baris Induk diakses oleh pemroses kueri, baris tersebut juga diperiksa untuk konflik pembaruan. Kesalahan muncul dalam kasus ini karena baris Induk yang direferensikan telah mengalami modifikasi yang dilakukan setelah transaksi snapshot dimulai. Perhatikan bahwa modifikasi tabel Induk tidak mempengaruhi kolom kunci asing itu sendiri.
Konflik tak terduga juga dapat terjadi jika perubahan pada tabel Anak merujuk ke baris Induk yang dibuat oleh transaksi bersamaan (dan transaksi itu dilakukan setelah transaksi snapshot dimulai).
Ringkasan:Rencana kueri yang menyertakan pemeriksaan kunci asing otomatis dapat menimbulkan kesalahan konflik jika baris yang direferensikan mengalami modifikasi apa pun (termasuk pembuatan!) sejak transaksi snapshot dimulai.
Masalah Tabel Pemotongan
Transaksi snapshot akan gagal dengan kesalahan jika ada tabel yang diaksesnya telah terpotong sejak transaksi dimulai. Ini berlaku bahkan jika tabel terpotong tidak memiliki baris untuk memulai, seperti yang ditunjukkan skrip di bawah ini:
CREATE TABLE dbo.AccessMe ( x integer NULL ); CREATE TABLE dbo.TruncateMe ( x integer NULL ); -- Connection 1 SET TRANSACTION ISOLATION LEVEL SNAPSHOT; BEGIN TRANSACTION; SELECT COUNT_BIG(*) FROM dbo.AccessMe; -- Connection 2 TRUNCATE TABLE dbo.TruncateMe; -- Connection 1 SELECT COUNT_BIG(*) FROM dbo.TruncateMe;
SELECT terakhir gagal dengan kesalahan:
Ini adalah efek samping halus lainnya yang harus diperiksa sebelum mengaktifkan isolasi snapshot pada database yang ada.
Lain kali
Postingan berikutnya (dan terakhir) dalam seri ini akan membahas tentang tingkat isolasi read uncommited (dikenal sebagai "nolock").
[ Lihat indeks untuk keseluruhan seri ]