Menelusuri, men-debug, dan memperbaiki Perselisihan Kunci Baris

12

Terlambat, saya telah menghadapi banyak pertengkaran kunci baris. Tabel dalam pertengkaran tampaknya merupakan tabel tertentu.

Inilah yang biasanya terjadi -

  • Pengembang 1 memulai transaksi dari layar ujung depan Oracle Forms
  • Pengembang 2 memulai transaksi lain, dari sesi berbeda menggunakan layar yang sama

~ 5 menit, ujung depan tampaknya tidak responsif. Memeriksa sesi menunjukkan pertentangan kunci baris. "Solusi" yang dilemparkan semua orang adalah dengan mematikan sesi: /

Sebagai pengembang basis data

  • Apa yang bisa dilakukan untuk menghilangkan pertikaian kunci baris?
  • Apakah mungkin untuk mengetahui baris mana dari prosedur tersimpan yang menyebabkan pertikaian kunci baris ini
  • Apa yang akan menjadi pedoman umum untuk mengurangi / menghindari / menghilangkan masalah seperti pengkodean itu?

Jika pertanyaan ini terasa terlalu terbuka / informasi tidak cukup, silakan mengedit / beri tahu - Saya akan melakukan yang terbaik untuk menambahkan beberapa informasi tambahan.


Tabel yang dimaksud adalah di bawah banyak sisipan dan pembaruan, saya akan mengatakan itu salah satu tabel yang paling sibuk. SP cukup kompleks - untuk menyederhanakan - mengambil data dari berbagai tabel, mengisinya ke dalam tabel kerja, banyak operasi aritmatika terjadi di meja kerja dan hasil dari tabel kerja dimasukkan / diperbarui ke dalam tabel yang dimaksud.


Versi database adalah Oracle Database 10g Edisi Enterprise Release 10.2.0.1.0 - 64bit. Alur logika dijalankan dalam urutan yang sama di kedua sesi, transaksi tidak dibuka terlalu lama (atau setidaknya saya pikir begitu), dan kunci terjadi selama eksekusi aktif transaksi.


Pembaruan: Jumlah baris tabel lebih besar dari yang saya harapkan, sekitar 3,1 juta baris. Juga, setelah menelusuri sesi saya menemukan bahwa beberapa pernyataan pembaruan untuk tabel ini tidak menggunakan indeks. Kenapa begitu - saya tidak yakin. Kolom direferensikan di mana klausa diindeks. Saat ini saya sedang membangun kembali indeks.

Sathyajith Bhat
sumber
1
@Sathya - dapatkah Anda menguraikan kompleksitas prosedur tersimpan? Apakah tabel yang dicurigai sedang diperbarui atau disisipkan dengan ketat?
CoderHawk
Apakah kunci asing berperan di sini? (Terkadang ini membutuhkan indeks) Apa versi basis data yang ada? Apakah aliran logika dijalankan dalam urutan yang sama di kedua sesi? Apakah transaksi tetap 'terbuka' untuk waktu yang lama? Apakah kunci terjadi selama pengguna berpikir waktu atau selama eksekusi aktif transaksi?
ik_zelf
@Sandy Saya sudah memperbarui pertanyaan
Sathyajith Bhat
@ik_zelf Saya telah memperbarui pertanyaan
Sathyajith Bhat
1
Tidak jelas bagi saya mengapa ini menjadi masalah - Oracle melakukan persis apa yang seharusnya dilakukan, yaitu membuat serialisasi akses ke satu baris. Jika seseorang memiliki baris itu, Anda dapat membaca versi sebelumnya, tetapi untuk menulis Anda harus menunggu mereka melepaskan kunci. Satu-satunya "perbaikan" untuk itu adalah a) tidak main-main dan COMMITatau ROLLBACKdalam waktu yang wajar atau b) mengatur sedemikian rupa sehingga orang yang sama tidak selalu menginginkan baris yang sama pada waktu yang sama.
Gayus

Jawaban:

10

Apakah mungkin untuk mengetahui baris mana dari prosedur tersimpan yang menyebabkan pertikaian kunci baris ini?

Tidak persis tetapi Anda bisa mendapatkan pernyataan SQL yang menyebabkan kunci dan pada gilirannya mengidentifikasi baris terkait dalam prosedur.

SELECT sid, sql_text
FROM v$session s
LEFT JOIN v$sql q ON q.sql_id=s.sql_id
WHERE state = 'WAITING' AND wait_class != 'Idle'
AND event = 'enq: TX - row lock contention';

Apa yang menjadi pedoman umum untuk mengurangi / menghindari / menghilangkan masalah dengan pengkodean?

Bagian Oracle Concepts Guide on locks mengatakan, "Sebuah baris dikunci hanya ketika dimodifikasi oleh seorang penulis." Sesi lain memperbarui baris yang sama kemudian akan menunggu sesi pertama COMMITatau ROLLBACKsebelum dapat melanjutkan. Untuk menghilangkan masalah Anda bisa membuat serialisasi pengguna, tetapi di sini ada beberapa hal yang dapat mengurangi masalah mungkin sampai tingkat itu tidak menjadi masalah.

  • COMMITlebih sering. Setiap COMMITrilis terkunci, jadi jika Anda dapat melakukan pembaruan dalam batch, kemungkinan sesi lain yang membutuhkan baris yang sama berkurang.
  • Pastikan Anda tidak memperbarui baris apa pun tanpa mengubah nilainya. Misalnya, UPDATE t1 SET f1=DECODE(f2,’a’,f1+1,f1);harus ditulis ulang sebagai yang lebih selektif (baca kunci lebih sedikit) UPDATE t1 SET f1=f1+1 WHERE f2=’a’;. Tentu saja jika mengubah pernyataan masih akan mengunci sebagian besar baris dalam tabel maka perubahan hanya akan memiliki manfaat keterbacaan.
  • Pastikan Anda menggunakan urutan daripada mengunci tabel untuk menambahkannya ke nilai saat ini tertinggi.
  • Pastikan Anda tidak menggunakan fungsi yang menyebabkan indeks tidak digunakan. Jika fungsi tersebut perlu dipertimbangkan menjadikannya sebagai indeks berbasis fungsi.
  • Pikirkan dalam set. Pertimbangkan apakah loop menjalankan blok PL / SQL melakukan pembaruan dapat ditulis ulang sebagai pernyataan pembaruan tunggal. Jika tidak maka mungkin pemrosesan massal dapat digunakan BULK COLLECT ... FORALL.
  • Kurangi pekerjaan yang dilakukan antara yang pertama UPDATEdan yang COMMIT. Misalnya, jika kode mengirim email setelah setiap pembaruan, pertimbangkan untuk mengantri email dan mengirimkannya setelah melakukan pembaruan.
  • Desain aplikasi untuk menangani menunggu dengan melakukan SELECT ... FOR UPDATE NOWAITatau WAIT 2. Anda kemudian dapat menangkap ketidakmampuan untuk mengunci baris dan memberi tahu pengguna bahwa sesi lain memodifikasi data yang sama.
Leigh Riffel
sumber
7

Saya akan memberikan jawaban dari sudut pandang pengembang.

Menurut pendapat saya, ketika Anda menemukan pertengkaran baris seperti yang Anda jelaskan, itu karena Anda memiliki bug dalam aplikasi Anda. Dalam kebanyakan kasus, jenis pertikaian ini adalah tanda kerentanan pembaruan yang hilang. Utas ini di AskTom menjelaskan konsep pembaruan yang hilang:

Pembaruan yang hilang terjadi ketika:

sesi 1: membacakan catatan Karyawan Tom

sesi 2: membacakan catatan Karyawan Tom

sesi 1: perbarui catatan karyawan Tom

sesi 2: perbarui catatan karyawan Tom

Sesi 2 akan MENULIS perubahan sesi 1 tanpa pernah melihatnya - menghasilkan pembaruan yang hilang.

Anda telah mengalami satu efek samping buruk dari pembaruan yang hilang: sesi 2 dapat diblokir karena sesi 1 belum dilakukan. Namun masalah utama adalah bahwa sesi 2 memperbarui catatan secara membabi buta. Misalkan kedua sesi mengeluarkan pernyataan:

UPDATE table SET col1=:col1, ..., coln=:coln WHERE id = :pk

Setelah kedua pernyataan, modifikasi session1 telah ditimpa, tanpa session2 telah diberitahu bahwa baris telah dimodifikasi oleh sesi 1.


Pembaruan yang hilang (dan efek samping pertengkaran) seharusnya tidak pernah terjadi, mereka 100% dapat dihindari. Anda harus menggunakan penguncian untuk mencegahnya dengan dua metode utama: penguncian optimis dan pesimistis .

1) Mengunci Pesimistis

Anda ingin memperbarui satu baris. Dalam mode ini Anda akan mencegah orang lain mengubah baris ini dengan meminta kunci pada baris itu ( SELECT ... FOR UPDATE NOWAITpernyataan). Jika baris sudah dimodifikasi, Anda akan mendapatkan pesan kesalahan, yang dapat Anda terjemahkan dengan anggun ke pengguna akhir (baris ini sedang dimodifikasi oleh pengguna lain). Jika baris tersedia, buat modifikasi Anda (PEMBARUAN), kemudian komit setiap kali transaksi Anda selesai.

2) Penguncian Optimis

Anda ingin memperbarui satu baris. Namun, Anda tidak ingin mempertahankan kunci pada baris itu, mungkin karena Anda menggunakan beberapa transaksi untuk memperbarui baris (aplikasi stateless berbasis web), atau mungkin Anda tidak ingin pengguna memegang kunci terlalu lama ( yang dapat menyebabkan orang lain diblokir). Dalam hal ini Anda tidak akan langsung meminta kunci. Anda akan menggunakan penanda untuk memastikan bahwa baris tidak berubah ketika pembaruan Anda akan dikeluarkan. Anda bisa menyimpan nilai semua kolom, atau menggunakan kolom timestamp yang diperbarui secara otomatis, atau kolom berbasis urutan. Apa pun pilihan Anda, ketika Anda akan melakukan pembaruan, Anda akan memastikan bahwa penanda pada baris itu tidak berubah dengan mengeluarkan kueri seperti:

SELECT <...>
  FROM table
 WHERE id = :id
   AND marker = :marker
   FOR UPDATE NOWAIT

Jika kueri mengembalikan baris, buat pembaruan Anda. Jika tidak, ini berarti seseorang telah memodifikasi baris sejak terakhir kali Anda menanyakannya. Anda harus memulai kembali proses dari awal.

Catatan: Jika Anda memiliki kepercayaan penuh atas semua aplikasi yang mengakses DB Anda, Anda dapat mengandalkan pembaruan langsung untuk penguncian optimis. Anda dapat mengeluarkan langsung:

UPDATE table
   SET <...>, 
       marker = marker + 1
 WHERE id = :id;

Jika pernyataan tidak memperbarui baris, Anda tahu bahwa seseorang telah mengubah baris ini dan Anda harus memulai dari awal.

Jika semua aplikasi menyetujui skema ini, Anda tidak akan pernah diblokir oleh orang lain dan Anda akan menghindari pembaruan buta. Namun, jika Anda tidak mengunci baris sebelumnya, Anda masih rentan terhadap penguncian tidak terbatas jika aplikasi lain, pekerjaan batch atau pembaruan langsung tidak menerapkan penguncian yang optimis. Inilah sebabnya saya menyarankan untuk selalu mengunci baris, apa pun pilihan skema penguncian Anda (hit kinerja dapat diabaikan karena Anda mengambil semua nilai termasuk rowid ketika Anda mengunci baris).

TL; DR

  • Memperbarui baris tanpa memiliki kunci di atasnya terlebih dahulu membuat aplikasi berpotensi "beku". Ini dapat dihindari jika semua DML ke DB menerapkan penguncian optimis atau pesimistis.
  • Verifikasi bahwa pernyataan SELECT mengembalikan nilai yang konsisten dengan SELECT sebelumnya (untuk menghindari masalah pembaruan yang hilang)
Vincent Malgrat
sumber
5

Jawaban ini mungkin memenuhi syarat untuk entri di The Daily WTF.

Benar, setelah menelusuri sesi dan mencari USER_SOURCE- saya melacak akar penyebabnya

  • Penyebabnya, secara logika cacat adalah cacat
  • Baru-baru ini, pernyataan pembaruan ditambahkan ke SP. Pernyataan pembaruan pada dasarnya akan memperbarui seluruh tabel. Tampaknya pengembang yang dimaksud lupa tentang menambahkan klausa hak untuk memperbarui pernyataan yang diperlukan.
  • Tabel yang diperbarui adalah seperti yang disebutkan di atas, salah satu tabel yang paling banyak ditransaksikan dan memiliki banyak catatan. Pembaruan akan memakan waktu lama, menyiksa.
  • Hasilnya adalah bahwa sesi lain tidak bisa mendapatkan kunci di atas meja dan akan duduk dalam pertengkaran-pertengkaran baris.
Sathyajith Bhat
sumber