Telusuri kegagalannya langkah demi langkah
Tekan Langkah berikutnya untuk maju satu pernyataan. Skor menunjukkan siapa melakukan apa dan kapan; panel di bawahnya menunjukkan apa yang dipegang mesin pada saat itu juga. Ganti mesin atau isolation level-nya dan langkah yang sama langsung dijalankan ulang.
Yang dijalankan
- Key
- 1 Dr. A
- 2 Dr. B
- Nilai
- 0 berhenti berjaga
- 1 sedang berjaga
Step 0, T1: begin.
Belum ada yang salah
PostgreSQL 16 · READ COMMITTEDSampai langkah ini belum ada satu pun definisi terbitan yang terpenuhi. Lanjutkan melangkah — sebuah anomali menjadi benar pada satu momen tertentu, dan tandanya akan muncul di langkah tempat itu terjadi.
Bagaimana cara membacanya?Sembunyikan keterangan
Cara membaca partitur
Tiap garis mendatar adalah satu transaksi — satu session basis data yang mengirim statement-nya dari kiri ke kanan. Segala sesuatu pada kolom tegak yang sama terjadi pada titik yang sama dalam eksekusi, jadi membaca lurus ke bawah memberi tahu Anda apa yang sedang kedua session lakukan terhadap satu sama lain.
- Satu garis per transaksi
- Tiap transaksi punya warna dan bentuk penanda sendiri, jadi keduanya tidak pernah hanya dibedakan lewat warna.
- Penanda kosong — sebuah pembacaan
- Nilai yang dilihatnya dicetak di atas penanda. Nilai itulah inti cerita pada hampir semua anomali.
- Penanda terisi — sebuah penulisan
- Insert, update, atau delete. Baris yang disentuhnya disebut di bawahnya.
- Satu garis tegak — commit
- Transaksi selesai dan pekerjaannya menjadi permanen serta terlihat oleh semua orang.
- Dua garis tegak — rollback
- Transaksi dibatalkan, entah atas permintaannya sendiri atau karena dimatikan oleh mesin.
- Busur putus-putus — sebuah penungguan
- Statement tidak bisa lanjut dan terhalang oleh lock. Ujung busurnya jatuh pada langkah yang akhirnya melepaskannya.
- Kurung di bawah paranada — berapa lama pandangan dibekukan
- Rentang snapshot transaksi itu: diambil sekali, pada langkah tempat ia dimulai, lalu dipakai untuk semua pembacaan sampai transaksinya berakhir. Level yang mengambil snapshot baru pada tiap statement tidak punya rentang untuk digambar, dan justru itulah perbedaan REPEATABLE READ dan READ COMMITTED dalam satu gambar.
- Lingkaran putus-putus pada kurung — commit yang tak terlihat
- Transaksi lain commit di dalam rentang Anda. Ia berhasil dan datanya sudah ada di tabel, dan transaksi ini tetap membaca versi yang lama — bukan bug, melainkan janji yang dibuat level tersebut.
- Garis tegas dengan segitiga — posisi Anda
- Langkah yang sedang ditampilkan pada panel di bawah. Menelusuri langkah menggeserkannya.
- Kurung merah — saat semuanya menjadi salah
- Letaknya di atas langkah ketika anomali menjadi tak terhindarkan. Merah tidak dipakai untuk hal lain di situs ini.
- Labelnya, misalnya w1[x]
- Notasi baku untuk jadwal: r berarti baca, w tulis, c commit, a abort. Angkanya adalah transaksinya, huruf dalam kurung siku adalah barisnya.
Geser sebuah tanda ke samping untuk mengubah urutan sisipan lalu menjalankannya ulang. Sebuah tanda hanya bisa bergerak di antara operasi tetangganya dalam transaksi yang sama — sebuah session mengirim statement-nya secara berurutan, jadi yang bisa Anda pilih hanyalah cara menyisipkannya. Tombol panah kiri dan kanan juga bisa dipakai.
Isi mesin pada langkah ini
Keadaan basis data pada statement di atas — bukan pada akhir eksekusi. Majukan dan mundurkan langkahnya untuk melihat semua ini berubah.
Rantai versi
Setiap penulisan membuat versi baru, bukan menggantikan yang lama. xmin adalah transaksi yang membuatnya, xmax transaksi yang menggantikan atau menghapusnya.
Aturannya: Anda melihat sebuah versi jika transaksi pada xmin sudah commit ketika snapshot Anda diambil, dan xmax kosong atau berisi transaksi yang belum commit saat itu. Telusuri versi dari yang terbaru dan ambil yang pertama lolos.
Aturannya, dalam kata-kata vendor
“When a transaction uses this isolation level, a SELECT query (without a FOR UPDATE/SHARE clause) sees only data committed before the query began; it never sees either uncommitted data or changes committed by concurrent transactions during the query's execution. In effect, a SELECT query sees a snapshot of the database as of the instant the query begins to run.”
| Nilai | xmin | xmax | Status versi ini |
|---|---|---|---|
| 1 | initial | — | hidup |
| Nilai | xmin | xmax | Status versi ini |
|---|---|---|---|
| 1 | initial | — | hidup |
Lock yang dipegang
Record lock mengunci sebuah baris. Gap lock mengunci ruang antar baris, dan gunanya mencegah sebuah insert muncul di tempat yang sudah dilihat pembaca.
Dua transaksi tidak bisa memegang lock yang berbenturan pada baris yang sama, jadi statement kedua menunggu sampai transaksi pertama berakhir. Menunggu bukan error: statement itu selesai belakangan, terhadap baris apa pun yang sudah jadi saat itu.
Aturannya, dalam kata-kata vendor
“In this case, the would-be updater will wait for the first updating transaction to commit or roll back (if it is still in progress). If the first updater rolls back, then its effects are negated and the second updater can proceed with updating the originally found row. If the first updater commits, the second updater will ignore the row if the first updater deleted it, otherwise it will attempt to apply its operation to the updated version of the row.”
Tidak ada lock yang dipegang pada langkah ini.
Snapshot
Transaksi mana saja yang sudah commit ketika tiap snapshot diambil.
Belum ada snapshot yang diambil.
Tabel yang sudah commit
1=1 2=1
Semua urutan penyisipan statement ini
Setiap session mengirim statement-nya sendiri secara berurutan, jadi satu-satunya kebebasan adalah bagaimana keduanya disisipkan — total 70 urutan. Semuanya dijalankan pada setiap level mesin ini.
- READ UNCOMMITTED36 / 70write-skew
- READ COMMITTED36 / 70write-skew
- REPEATABLE READ36 / 70write-skew
- SNAPSHOTlevel tidak ada
- SERIALIZABLE0 / 70tidak ada anomali pada urutan mana pun36 malah di-abort
Ini adalah cacah urutan, bukan peluang. Urutan penyisipan di dunia nyata tidak terdistribusi merata, dan tidak ada satu pun di sini yang menyatakan seberapa sering sesuatu terjadi di bawah beban — hanya apa yang mungkin terjadi sama sekali.
Apa yang dilakukan mesin sungguhan
Statement yang persis sama ini dijalankan terhadap mesin ini di dalam container, lalu apa yang dilakukannya dicatat. Bukan pemeragaan ulang — rekaman di bawah adalah bukti yang harus dicocoki oleh model.
16.14direkam 2026-08-06postgres:16-alpine
| Langkah | Mesin mengembalikan | Model ini menyatakan |
|---|---|---|
| 0 b1 | ok | ok |
| 1 b2 | ok | ok |
| 2 r1[P:1..2] | {1=1, 2=1} | {1=1, 2=1} |
| 3 r2[P:1..2] | {1=1, 2=1} | {1=1, 2=1} |
| 4 w1[1=0] | ok | ok |
| 5 w2[2=0] | ok | ok |
| 6 c1 | ok | ok |
| 7 c2 | ok | ok |
Kedua kolom itu sepakat karena build-nya gagal kalau tidak — pnpm test:oracle membandingkan setiap eksekusi yang direkam dengan model, kolom demi kolom, dan ketidaksepakatan adalah kesalahan model, tidak pernah kesalahan rekaman.
Schedule yang sama pada level lainnya
Setiap level pada mesin ini, dijalankan atas statement yang persis sama, beserta langkah pertama yang membuatnya tidak lagi sama dengan eksekusi di atas. Pilih salah satu untuk berpindah ke sana.
Tidak conflict-serializable
T1 must run before T2, and T2 must run before T1 — which is impossible, so no order of these transactions one after another produces this outcome.
Jadwal jaga yang menjadi kosong
Situasinya: Dua dokter sedang berjaga dan setidaknya satu harus tetap berjaga. Masing-masing membuka jadwal, melihat bahwa yang lain berjaga, lalu mengeluarkan dirinya. Keduanya commit.
Pelajarannya: Inilah write skew: anomali yang tidak ada dalam daftar ANSI. REPEATABLE READ pada PostgreSQL adalah snapshot isolation, dan mengizinkannya: kedua transaksi commit dan tidak ada yang berjaga. Hanya SERIALIZABLE yang menangkapnya, dan caranya dengan membatalkan transaksi kedua yang commit karena read/write dependency — bukan dengan memblokir. MySQL InnoDB juga mengizinkannya di REPEATABLE READ, dan di SERIALIZABLE ia tidak mendeteksi apa pun — ia deadlock. SQL Server mengizinkannya di SNAPSHOT. Dan Oracle mengakhiri perdebatan: ia mengizinkannya di level yang bernama SERIALIZABLE, karena SERIALIZABLE pada Oracle adalah snapshot isolation.