Lompat ke konten

Interaktif · Berjalan di peramban · Tanpa daftar akun

Lihat dua transaksi diam-diam saling merusak.

Simulator basis data yang interaktif. Jalankan dua transaksi berdampingan, selangkah demi selangkah, dan lihat persis di mana basis data memberi jawaban yang salah — lalu ganti mesin atau isolation level-nya dan lihat langkah yang sama berakhir berbeda.

Membuka schedule di bawah, berjalan pada PostgreSQL 16 di REPEATABLE READ.

01234567T1commitT2commitb1b2r1[P:1..2]{1,2}r2[P:1..2]{1,2}w1[1=0]w2[2=0]c1c2anomali
Dua transaksi, delapan langkah, dari kiri ke kanan. Keduanya membaca daftar jaga dan melihat dua dokter yang sama; masing-masing lalu menulis baris yang berbeda. Tidak ada yang bertabrakan, keduanya commit, dan tanda merah menandai titik saat hal itu tak lagi terhindarkan.

Satu kegagalan yang mudah dibayangkan

Aturan rumah sakit: minimal satu dokter harus tetap berjaga. Saat ini ada dua yang berjaga. Keduanya memutuskan pulang pada saat yang sama.

  1. Dr ADokter A memeriksa berapa dokter yang sedang berjaga. Jawabannya dua.
  2. Dr BDokter B memeriksa pada detik yang sama. Dua juga.
  3. Dr AA melihat masih ada satu dokter lain, jadi A berhenti berjaga.
  4. Dr BB melihat masih ada satu dokter lain, jadi B berhenti berjaga.

Tidak ada yang berjaga. Tak satu pun transaksi menyentuh baris milik yang lain, tidak ada yang terkunci, tidak ada kesalahan yang muncul, dan keduanya berhasil commit. Inilah write skew, dan sebagian besar basis data mengizinkannya pada level yang kemungkinan besar Anda pakai sekarang.

Dengan bahasa sederhana

Basis data Anda menjalankan banyak transaksi sekaligus. Agar tetap cepat, ia membiarkan tiap transaksi melihat versi pekerjaan transaksi lain yang sedikit basi atau setengah jadi. Biasanya itu tidak terasa. Kadang dua transaksi bertumpang tindih pada urutan yang pas salahnya, dan hasilnya jawaban yang memang keliru — uang terhitung dua kali, aturan yang ternyata tidak dijaga siapa pun, baris yang lenyap di tengah pembacaan. Setelan yang menentukan seberapa banyak tumpang tindih yang diizinkan itulah isolation level. Situs ini membuat Anda bisa sengaja memicu kegagalan itu dan menyaksikannya terjadi.

Satu eksekusi, dari awal sampai akhir

Ini keseluruhannya, langkah demi langkah, dengan angka yang sebenarnya — dihasilkan dengan menjalankan schedule ini melalui simulator yang sama dengan yang dipakai seluruh situs. Tidak ada yang perlu Anda tekan di sini. Perhatikan bahwa tidak ada satu pun yang gagal: setiap statement di bawah ini berhasil.

PostgreSQL 16 · REPEATABLE READ

Key 1
Dr. A
Key 2
Dr. B
= 0
berhenti berjaga
= 1
sedang berjaga
  1. 0b1begin
  2. 1b2begin
  3. 2r1[P:1..2]range read over keys 1..2{1=1, 2=1}

    bagian dari tabel sebelum run dimulai

  4. 3r2[P:1..2]range read over keys 1..2{1=1, 2=1}

    bagian dari tabel sebelum run dimulai

  5. 4w1[1=0]write key 1 = 0
  6. 5w2[2=0]write key 2 = 0

    T1 wrote key 1 at step 4 and T2 wrote key 2 at step 5. Each had already read the row the other wrote, neither saw the other's write, and no row was written twice — so nothing conflicted and both committed.

  7. 6c1commit
  8. 7c2commit

Tabelnya berakhir menjadi 1=0 2=0

Tidak ada transaksi yang membaca data yang belum commit, tidak ada yang menulis baris yang ditulis transaksi lain, tidak ada lock yang berebut dan tidak ada error yang muncul. Setiap pembacaan benar per saat snapshot-nya diambil. Kedua snapshot itu diambil sebelum kedua penulisan terjadi, dan itulah seluruh bug-nya.

Delapan langkah yang sama, satu level lebih tinggi

Tidak ada yang berubah pada schedule-nya — statement yang sama, urutan yang sama, data yang sama. Hanya isolation level-nya yang berbeda, dan eksekusi di bawah ini dihasilkan dengan cara yang sama seperti yang di atas.

PostgreSQL 16 · SERIALIZABLE

  1. 0b1
  2. 1b2
  3. 2r1[P:1..2]
  4. 3r2[P:1..2]
  5. 4w1[1=0]
  6. 5w2[2=0]
  7. 6c1
  8. 7c240001 could not serialize access due to read/write dependencies among transactions

Tabelnya berakhir menjadi 1=0 2=1

Satu dokter tetap berjaga, karena basis data menolak commit yang kedua alih-alih mengizinkannya.

Cara memakainya

  1. 1

    Pilih satu kegagalan

    Tiap skenario adalah kegagalan klasik yang nyata, ditulis sebagai dua atau tiga transaksi dengan statement-nya disisipkan dalam urutan tertentu.

  2. 2

    Telusuri langkah demi langkah

    Tekan Berikutnya untuk maju satu statement. Partitur menunjukkan siapa melakukan apa dan kapan; panel di bawahnya menunjukkan keadaan mesin pada saat itu juga.

  3. 3

    Ubah satu hal

    Ganti mesinnya atau naikkan isolation level-nya, dan langkah yang sama langsung dijalankan ulang. Apa yang berubah — dan apa yang keras kepala tidak berubah — itulah seluruh pelajarannya.

Write skew adalah intinya

Dua transaksi membaca data yang sama, masing-masing memverifikasi sebuah constraint, masing-masing menulis baris yang berbeda, dan keduanya commit. Tidak ada baris yang sama, tidak ada perebutan lock, tidak ada bentrokan versi — dan constraint itu dilanggar oleh gabungannya. Anomali ini tidak ada dalam daftar ANSI, diizinkan oleh snapshot isolation, dan paling mungkin merugikan aplikasi nyata.

Nama level berarti berbeda di mesin yang berbeda

REPEATABLE READ pada PostgreSQL adalah snapshot isolation: ia mencegah phantom — yang tidak diwajibkan ANSI — dan mengizinkan write skew. READ UNCOMMITTED pada PostgreSQL diam-diam berperilaku sebagai READ COMMITTED. SERIALIZABLE pada Oracle adalah snapshot isolation, jadi ia mengizinkan write skew meski namanya begitu. Tidak satu pun dari ini bisa diturunkan dari pengetahuan umum tentang MVCC.

Diuji terhadap basis data sungguhan

Setiap jadwal di sini telah dijalankan terhadap mesin sungguhan di dalam container, dan apa yang dilakukannya — nilai yang dibaca, penungguan, kode kesalahan, transaksi mana yang dibatalkan, tabel akhirnya — disimpan sebagai fixture yang menguji model ini. Bila model dan basis data berbeda, modelnya yang salah.

Kegagalan yang bisa disebut namanya di sini

Rumus di samping tiap nama adalah notasi baku untuk sebuah jadwal: r berarti baca, w berarti tulis, c berarti commit; angkanya menunjukkan transaksi mana yang melakukannya, dan huruf dalam kurung siku menunjukkan barisnya. Jadi w1[x] r2[x] dibaca “transaksi 1 menulis baris x, lalu transaksi 2 membaca baris x”.

  • Dirty writeP0

    w1[x] ... w2[x] ... ((c1 or a1) and (c2 or a2) in any order)

    tidak ada dalam daftar ANSI SQL-92

  • Dirty readP1

    w1[x] ... r2[x] ... (c1 or a1)

    ada dalam daftar ANSI SQL-92

  • Lost updateP4

    r1[x] ... w2[x] ... w1[x] ... c1

    tidak ada dalam daftar ANSI SQL-92

  • Non-repeatable readP2

    r1[x] ... w2[x] ... c2 ... r1[x]

    ada dalam daftar ANSI SQL-92

  • Phantom readP3

    r1[P] ... w2[y in P] ... c2 ... r1[P]

    ada dalam daftar ANSI SQL-92

  • Read skewA5A

    r1[x] ... w2[x] ... w2[y] ... c2 ... r1[y]

    tidak ada dalam daftar ANSI SQL-92

  • Write skewA5B

    r1[x] ... r2[y] ... w1[y] ... w2[x] ... (c1 and c2)

    tidak ada dalam daftar ANSI SQL-92