TL; DR: Pertanyaan di bawah ini bermuara pada: Ketika memasukkan baris, apakah ada jendela peluang antara pembuatanIdentity
nilai baru dan penguncian kunci baris yang sesuai dalam indeks berkerumun, di mana pengamat eksternal dapat melihat yang lebih baru Identity
nilai dimasukkan oleh transaksi bersamaan? (Dalam SQL Server.)
Versi terperinci
Saya memiliki tabel SQL Server dengan Identity
kolom yang disebut CheckpointSequence
, yang merupakan kunci dari indeks berkerumun tabel (yang juga memiliki sejumlah indeks nonclustered tambahan). Baris dimasukkan ke dalam tabel oleh beberapa proses dan utas bersamaan (pada tingkat isolasi READ COMMITTED
, dan tanpa IDENTITY_INSERT
). Pada saat yang sama, ada proses membaca baris secara berkala dari indeks berkerumun, dipesan oleh CheckpointSequence
kolom itu (juga pada tingkat isolasi READ COMMITTED
, dengan READ COMMITTED SNAPSHOT
opsi dimatikan).
Saat ini saya mengandalkan fakta bahwa proses membaca tidak pernah bisa "melewati" pos pemeriksaan. Pertanyaan saya adalah: Dapatkah saya mengandalkan properti ini? Dan jika tidak, apa yang bisa saya lakukan untuk mewujudkannya?
Contoh: Ketika baris dengan nilai identitas 1, 2, 3, 4, dan 5 dimasukkan, pembaca tidak boleh melihat baris dengan nilai 5 sebelum melihat yang dengan nilai 4. Tes menunjukkan bahwa kueri, yang berisi ORDER BY CheckpointSequence
klausa ( dan WHERE CheckpointSequence > -1
klausa), andal memblokir kapan pun baris 4 dibaca, tetapi belum berkomitmen, bahkan jika baris 5 sudah dikomit.
Saya percaya bahwa setidaknya secara teori, mungkin ada kondisi ras di sini yang dapat menyebabkan asumsi ini rusak. Sayangnya, dokumentasi pada Identity
tidak mengatakan banyak tentang cara Identity
kerjanya dalam konteks beberapa transaksi bersamaan, hanya mengatakan "Setiap nilai baru dihasilkan berdasarkan pada seed & increment saat ini." dan "Setiap nilai baru untuk transaksi tertentu berbeda dari transaksi bersamaan lainnya di atas meja." ( MSDN )
Alasan saya adalah, itu harus bekerja entah bagaimana seperti ini:
- Transaksi dimulai (baik secara eksplisit atau implisit).
- Nilai identitas (X) dihasilkan.
- Kunci baris terkait diambil pada indeks berkerumun berdasarkan pada nilai identitas (kecuali jika eskalasi kunci menendang, dalam hal ini seluruh tabel terkunci).
- Baris dimasukkan.
- Transaksi dilakukan (mungkin cukup banyak waktu kemudian), sehingga kunci dihapus lagi.
Saya pikir antara langkah 2 dan 3, ada jendela yang sangat kecil di mana
- sesi bersamaan dapat menghasilkan nilai identitas berikutnya (X + 1) dan menjalankan semua langkah yang tersisa,
- sehingga memungkinkan pembaca datang tepat pada titik waktu itu untuk membaca nilai X + 1, kehilangan nilai X.
Tentu saja, kemungkinan ini tampaknya sangat rendah; tapi tetap saja - itu bisa terjadi. Atau mungkinkah itu?
(Jika Anda tertarik dengan konteksnya: Ini adalah implementasi dari SQL Persistence Engine NEventStore. NEventStore mengimplementasikan toko acara khusus-append di mana setiap acara mendapatkan nomor urut pos pemeriksaan naik yang baru. Klien membaca acara dari toko acara yang dipesan oleh pos pemeriksaan) untuk melakukan perhitungan dari segala macam. Begitu suatu peristiwa dengan pos pemeriksaan X telah diproses, klien hanya mempertimbangkan peristiwa "baru", yaitu peristiwa dengan pos pemeriksaan X +1 dan di atasnya. Oleh karena itu, sangat penting bahwa peristiwa tidak pernah dapat dilewati, karena mereka tidak akan pernah dipertimbangkan lagi. Saat ini saya mencoba untuk menentukan apakah Identity
implementasi pos pemeriksaan berbasis memenuhi persyaratan ini. Ini adalah pernyataan SQL yang tepat digunakan : Skema , permintaan Writer ,Permintaan Pembaca .)
Jika saya benar dan situasi yang dijelaskan di atas dapat muncul, saya hanya dapat melihat dua opsi untuk berurusan dengan mereka, yang keduanya tidak memuaskan:
- Saat melihat nilai urutan pos pemeriksaan X + 1 sebelum melihat X, singkirkan X + 1 dan coba lagi nanti. Namun, karena
Identity
tentu saja dapat menghasilkan kesenjangan (misalnya, ketika transaksi dibatalkan), X mungkin tidak pernah datang. - Jadi, pendekatan yang sama, tetapi menerima kesenjangan setelah n milidetik. Namun, nilai n apa yang harus saya asumsikan?
Ada ide yang lebih baik?
sumber
Jawaban:
Iya nih.
The alokasi nilai-nilai identitas independen dari transaksi pengguna mengandung . Ini adalah salah satu alasan nilai-nilai identitas dikonsumsi bahkan jika transaksi dibatalkan. Operasi kenaikan itu sendiri dilindungi oleh kait untuk mencegah korupsi, tetapi sejauh itulah perlindungan.
Dalam keadaan spesifik implementasi Anda, alokasi identitas (panggilan ke
CMEDSeqGen::GenerateNewValue
) dilakukan sebelum transaksi pengguna untuk memasukkan bahkan diaktifkan (dan sebelum kunci diambil).Dengan menjalankan dua sisipan bersamaan dengan debugger yang dilampirkan untuk memungkinkan saya membekukan satu utas tepat setelah nilai identitas bertambah dan dialokasikan, saya dapat mereproduksi skenario di mana:
Setelah langkah 3, kueri yang menggunakan row_number di bawah penguncian read berkomitmen mengembalikan yang berikut:
Dalam implementasi Anda, ini akan mengakibatkan ID Pos Pemeriksaan 3 dilewati secara tidak benar.
Jendela misopportunity relatif kecil, tetapi ada. Untuk memberikan skenario yang lebih realistis daripada memiliki debugger terpasang: Sebuah thread permintaan mengeksekusi dapat menghasilkan penjadwal setelah langkah 1 di atas. Ini memungkinkan utas kedua mengalokasikan nilai identitas, menyisipkan, dan melakukan, sebelum utas asli melanjutkan untuk melakukan sisipannya.
Untuk kejelasan, tidak ada kunci atau objek sinkronisasi lain yang melindungi nilai identitas setelah dialokasikan dan sebelum digunakan. Misalnya, setelah langkah 1 di atas, transaksi konkuren dapat melihat nilai identitas baru menggunakan fungsi T-SQL seperti
IDENT_CURRENT
sebelum baris ada dalam tabel (bahkan tidak berkomitmen).Pada dasarnya, tidak ada lagi jaminan seputar nilai identitas selain yang didokumentasikan :
Itu benar-benar itu.
Jika pemrosesan FIFO transaksional ketat diperlukan, Anda mungkin tidak punya pilihan selain membuat cerita bersambung secara manual. Jika aplikasi memiliki persyaratan kurang satu, Anda memiliki lebih banyak opsi. Pertanyaannya tidak 100% jelas dalam hal itu. Namun demikian, Anda dapat menemukan beberapa informasi yang berguna dalam artikel Remus Rusanu Menggunakan Tabel sebagai Antrian .
sumber
Ketika Paul White menjawab sepenuhnya benar, ada kemungkinan untuk "melewatkan" baris identitas sementara. Ini hanya sepotong kecil kode untuk mereproduksi kasus ini untuk Anda sendiri.
Buat database dan tabel tes:
Lakukan penyisipan dan seleksi bersamaan pada tabel ini dalam program konsol C #:
Konsol ini mencetak baris untuk setiap kasus ketika salah satu utas pembacaan "melewatkan" entri.
sumber
IDENTITY
akan menghasilkan kesenjangan (seperti memutar kembali transaksi), garis yang dicetak memang menunjukkan nilai "dilewati" (atau setidaknya mereka lakukan ketika saya berlari dan memeriksanya di mesin saya). Sampel repro yang sangat bagus!Yang terbaik adalah tidak mengharapkan identitas menjadi berurutan karena ada banyak skenario yang dapat meninggalkan celah. Lebih baik mempertimbangkan identitas seperti angka abstrak dan tidak melampirkan arti bisnis apa pun padanya.
Pada dasarnya, kesenjangan dapat terjadi jika Anda memutar kembali operasi INSERT (atau menghapus baris secara eksplisit), dan duplikat dapat terjadi jika Anda mengatur properti tabel IDENTITY_INSERT ke ON.
Kesenjangan dapat terjadi ketika:
Properti identitas pada kolom tidak pernah dijamin:
• Keunikan
• Nilai berturut-turut dalam suatu transaksi. Jika nilai harus berturut-turut maka transaksi harus menggunakan kunci eksklusif di atas meja atau menggunakan tingkat isolasi SERIALIZABLE.
• Nilai berturut-turut setelah server restart.
• Penggunaan kembali nilai.
Jika Anda tidak dapat menggunakan nilai identitas karena hal ini, buat tabel terpisah yang menyimpan nilai saat ini dan kelola akses ke tabel dan penetapan angka dengan aplikasi Anda. Ini memang berpotensi memengaruhi kinerja.
https://msdn.microsoft.com/en-us/library/ms186775(v=sql.105).aspx
https://msdn.microsoft.com/en-us/library/ms186775(v=sql.110) .aspx
sumber
ORDER BY CheckpointSequence
klausa (yang merupakan urutan indeks berkerumun). Saya pikir itu bermuara pada pertanyaan apakah generasi nilai Identitas bagaimanapun terkait dengan kunci yang diambil oleh pernyataan INSERT, atau jika ini hanya dua tindakan yang tidak berhubungan yang dilakukan oleh SQL Server satu demi satu.SELECT ... FROM Commits WHERE CheckpointSequence > ... ORDER BY CheckpointSequence
. Saya tidak berpikir permintaan ini akan membaca melewati baris yang terkunci 4, atau akankah itu? (Dalam percobaan saya, itu memblokir ketika kueri mencoba untuk mendapatkan kunci KUNCI untuk baris 4.)Saya menduga bahwa itu kadang-kadang dapat menyebabkan masalah, masalah yang menjadi lebih buruk ketika server berada di bawah beban berat. Pertimbangkan dua transaksi:
Dalam skenario di atas, LAST_READ_ID Anda akan menjadi 6, jadi 5 tidak akan pernah dibaca.
sumber
Menjalankan skrip ini:
Di bawah ini adalah kunci yang saya lihat diperoleh dan dirilis saat ditangkap oleh sesi Peristiwa Diperpanjang:
Perhatikan bahwa kunci kunci RI_N diperoleh tepat sebelum kunci tombol X untuk baris baru dibuat. Kunci rentang berumur pendek ini akan mencegah sisipan konkuren mendapatkan kunci RI_N KEY lainnya karena kunci RI_N tidak kompatibel. Jendela yang Anda sebutkan antara langkah 2 dan 3 tidak menjadi masalah karena kunci rentang diperoleh sebelum kunci baris pada kunci yang baru dibuat.
Selama Anda
SELECT...ORDER BY
memulai pemindaian sebelum baris yang baru saja dimasukkan, saya akan mengharapkan perilaku yang Anda inginkan diREAD COMMITTED
tingkat isolasi default selamaREAD_COMMITTED_SNAPSHOT
opsi database dimatikan.sumber
RangeI_N
yang kompatibel , yaitu, jangan memblokir satu sama lain (kunci itu sebagian besar ada untuk memblokir pada pembaca serializable yang ada).Dari pemahaman saya tentang SQL Server perilaku default adalah untuk permintaan kedua untuk tidak menampilkan hasil apa pun sampai permintaan pertama telah dilakukan. Jika kueri pertama melakukan ROLLBACK alih-alih KOMIT, maka Anda akan memiliki ID yang hilang di kolom Anda.
Konfigurasi Dasar
Tabel Database
Saya membuat tabel database dengan struktur berikut:
Tingkat Isolasi Basis Data
Saya memeriksa tingkat isolasi basis data saya dengan pernyataan berikut:
Yang mengembalikan hasil berikut untuk database saya:
(Ini adalah pengaturan default untuk database di SQL Server 2012)
Tes Script
Skrip berikut dieksekusi menggunakan pengaturan klien SSMS SQL Server standar dan pengaturan SQL Server standar.
Pengaturan koneksi klien
Klien telah diatur untuk menggunakan Level Isolasi Transaksi
READ COMMITTED
sesuai Opsi Kueri di SSMS.Pertanyaan 1
Kueri berikut dijalankan di jendela Kueri dengan SPID 57
Pertanyaan 2
Permintaan berikut dijalankan di jendela Query dengan SPID 58
Permintaan tidak selesai dan sedang menunggu kunci eXclusive untuk dirilis pada HALAMAN.
Script untuk menentukan penguncian
Script ini menampilkan penguncian yang terjadi pada objek database untuk dua transaksi:
Dan inilah hasilnya:
Hasil penelitian menunjukkan bahwa jendela permintaan satu (SPID 57) memiliki kunci Bersama (S) pada DATABASE kunci yang dimaksudkan eXlusive (IX) pada OBJECT, kunci yang dimaksudkan eXlusive (IX) pada PAGE yang ingin disisipkan ke dan eXclusive mengunci (X) pada KUNCI itu telah dimasukkan, tetapi belum dilakukan.
Karena data yang tidak dikomit, kueri kedua (SPID 58) memiliki kunci Bersama (S) pada tingkat DATABASE, kunci yang Dibagikan Bersama (IS) pada OBJECT, kunci yang Dibagikan Bersama (IS) pada halaman yang Dibagikan (S) ) mengunci KUNCI dengan status permintaan TUNGGU.
Ringkasan
Kueri di jendela kueri pertama dijalankan tanpa melakukan. Karena kueri kedua hanya dapat
READ COMMITTED
data itu menunggu baik sampai waktu habis terjadi atau sampai transaksi telah dilakukan dalam permintaan pertama.Ini dari pemahaman saya tentang perilaku default Microsoft SQL Server.
Anda harus memperhatikan bahwa ID tersebut memang berurutan untuk pembacaan selanjutnya oleh pernyataan SELECT jika pernyataan pertama BERKOMITMEN.
Jika pernyataan pertama melakukan ROLLBACK maka Anda akan menemukan ID yang hilang dalam urutan, tetapi masih dengan ID dalam urutan menaik (asalkan Anda membuat INDEX dengan opsi default atau ASC pada kolom ID).
Memperbarui:
(Terus terang) Ya, Anda bisa mengandalkan kolom identitas yang berfungsi dengan benar, sampai Anda menemukan masalah. Hanya ada satu HOTFIX tentang SQL Server 2000 dan kolom identitas di situs web Microsoft.
Jika Anda tidak dapat mengandalkan pemutakhiran kolom identitas dengan benar, saya pikir akan ada lebih banyak perbaikan terbaru atau tambalan di situs web Microsoft.
Jika Anda memiliki Kontrak Dukungan Microsoft, Anda selalu dapat membuka Kasus Penasihat dan meminta informasi tambahan.
sumber
Identity
dan perolehan kunci KUNCI pada baris (di mana membaca / penulis bersamaan dapat jatuh ke dalam). Saya tidak berpikir ini terbukti mustahil oleh pengamatan Anda karena orang tidak dapat menghentikan eksekusi permintaan dan menganalisis kunci selama jendela waktu ultra-pendek.