Saya memiliki tabel innoDB yang mencatat pengguna online. Itu diperbarui pada setiap halaman refresh oleh pengguna untuk melacak halaman mana mereka berada dan tanggal akses terakhir mereka ke situs. Saya kemudian memiliki cron yang berjalan setiap 15 menit untuk HAPUS catatan lama.
Saya mendapat 'Deadlock ditemukan ketika mencoba untuk mendapatkan kunci; coba mulai kembali transaksi 'selama sekitar 5 menit semalam dan tampaknya ketika menjalankan INSERT ke dalam tabel ini. Adakah yang bisa menyarankan cara menghindari kesalahan ini?
=== EDIT ===
Berikut ini pertanyaan yang sedang berjalan:
Kunjungan Pertama ke situs:
INSERT INTO onlineusers SET
ip = 123.456.789.123,
datetime = now(),
userid = 321,
page = '/thispage',
area = 'thisarea',
type = 3
Di setiap halaman segarkan:
UPDATE onlineusers SET
ips = 123.456.789.123,
datetime = now(),
userid = 321,
page = '/thispage',
area = 'thisarea',
type = 3
WHERE id = 888
Cron setiap 15 menit:
DELETE FROM onlineusers WHERE datetime <= now() - INTERVAL 900 SECOND
Ini kemudian menghitung jumlah untuk mencatat beberapa statistik (yaitu: anggota online, pengunjung online).
Jawaban:
Salah satu trik mudah yang dapat membantu sebagian besar deadlock adalah menyortir operasi dalam urutan tertentu.
Anda mendapatkan jalan buntu ketika dua transaksi mencoba untuk mengunci dua kunci pada pesanan yang berlawanan, yaitu:
Jika keduanya berjalan secara bersamaan, koneksi 1 akan mengunci kunci (1), koneksi 2 akan mengunci kunci (2) dan setiap koneksi akan menunggu yang lain untuk melepaskan kunci -> deadlock.
Sekarang, jika Anda mengubah kueri Anda sehingga koneksi akan mengunci kunci pada urutan yang sama, yaitu:
tidak mungkin mendapatkan jalan buntu.
Jadi ini yang saya sarankan:
Pastikan Anda tidak memiliki pertanyaan lain yang mengunci akses lebih dari satu kunci sekaligus kecuali untuk pernyataan hapus. jika Anda melakukannya (dan saya curiga Anda melakukannya), pesan WHERE mereka di (k1, k2, .. kn) dalam urutan menaik.
Perbaiki pernyataan hapus Anda agar berfungsi dalam urutan menaik:
Perubahan
Untuk
Satu hal yang perlu diingat adalah bahwa dokumentasi mysql menyarankan bahwa jika terjadi kebuntuan, klien harus mencoba ulang secara otomatis. Anda dapat menambahkan logika ini ke kode klien Anda. (Katakan, 3 ulangi kesalahan khusus ini sebelum menyerah).
sumber
Kebuntuan terjadi ketika dua transaksi saling menunggu untuk mendapatkan kunci. Contoh:
Ada banyak pertanyaan dan jawaban tentang kebuntuan. Setiap kali Anda memasukkan / memperbarui / atau menghapus baris, kunci diperoleh. Untuk menghindari kebuntuan, Anda harus memastikan bahwa transaksi bersamaan tidak memperbarui baris dalam urutan yang dapat menyebabkan kebuntuan. Secara umum, cobalah untuk mendapatkan kunci selalu dalam urutan yang sama bahkan dalam transaksi yang berbeda (misalnya selalu tabel A dulu, lalu tabel B).
Alasan lain untuk kebuntuan dalam basis data adalah indeks yang hilang . Ketika sebuah baris dimasukkan / perbarui / hapus, basis data perlu memeriksa kendala relasional, yaitu, pastikan hubungan konsisten. Untuk melakukannya, database perlu memeriksa kunci asing di tabel terkait. Ini mungkin menghasilkan kunci lain yang diperoleh dari baris yang dimodifikasi. Pastikan untuk selalu memiliki indeks pada kunci asing (dan tentu saja kunci utama), jika tidak maka akan menghasilkan kunci tabel, bukan kunci baris . Jika kunci meja terjadi, pertikaian kunci lebih tinggi dan kemungkinan kebuntuan meningkat.
sumber
Kemungkinan bahwa pernyataan penghapusan akan memengaruhi sebagian besar dari total baris dalam tabel. Akhirnya ini mungkin menyebabkan kunci meja diperoleh saat menghapus. Berpegang pada kunci (dalam hal ini kunci baris atau halaman) dan mendapatkan lebih banyak kunci selalu merupakan risiko kebuntuan. Namun saya tidak bisa menjelaskan mengapa pernyataan penyisipan mengarah ke eskalasi kunci - mungkin ada hubungannya dengan halaman splitting / menambahkan, tetapi seseorang yang mengetahui MySQL lebih baik harus mengisi di sana.
Sebagai permulaan, ada baiknya mencoba memperoleh kunci meja segera untuk pernyataan penghapusan. Lihat LOCK TABLES dan masalah penguncian tabel .
sumber
Anda mungkin mencoba menjalankan
delete
pekerjaan itu dengan terlebih dahulu memasukkan kunci dari setiap baris untuk dihapus ke tabel temp seperti kodesemu iniMemecahnya seperti ini kurang efisien tetapi menghindari kebutuhan untuk memegang kunci-kunci selama
delete
.Juga, modifikasi
select
kueri Anda untuk menambahkanwhere
klausa tidak termasuk baris yang lebih tua dari 900 detik. Ini menghindari ketergantungan pada pekerjaan cron dan memungkinkan Anda menjadwal ulang untuk menjalankan lebih jarang.Teori tentang kebuntuan: Saya tidak memiliki banyak latar belakang di MySQL tapi begini ... Saya
delete
akan memegang kunci-rentang kunci untuk datetime, untuk mencegah agar baris yang cocok denganwhere
klausulnya tidak ditambahkan di tengah transaksi , dan ketika menemukan baris untuk menghapusnya akan berusaha mendapatkan kunci pada setiap halaman yang dimodifikasinya. Theinsert
akan mendapatkan kunci pada halaman itu memasukkan ke dalam, dan kemudian berusaha untuk memperoleh kunci tombol. Biasanyainsert
akan menunggu dengan sabar untuk kunci kunci untuk membuka tetapi ini akan menemui jalan buntu jikadelete
mencoba untuk mengunci halaman yang sama yanginsert
digunakan karenadelete
kebutuhan kunci halaman daninsert
kebutuhan kunci kunci. Ini sepertinya tidak tepat untuk memasukkan,delete
daninsert
menggunakan rentang datetime yang tidak tumpang tindih jadi mungkin sesuatu yang lain sedang terjadi.http://dev.mysql.com/doc/refman/5.1/en/innodb-next-key-locking.html
sumber
Jika seseorang masih berjuang dengan masalah ini:
Saya menghadapi masalah serupa di mana 2 permintaan mengenai server pada saat bersamaan. Tidak ada situasi seperti di bawah ini:
Jadi, saya bingung mengapa kebuntuan terjadi.
Kemudian saya menemukan bahwa ada hubungan anak induk pengiriman antara 2 tabel karena kunci asing. Ketika saya memasukkan catatan di tabel anak, transaksi memperoleh kunci di baris tabel induk. Segera setelah itu saya mencoba untuk memperbarui baris induk yang memicu peningkatan kunci ke satu EKSKLUSIF. Karena transaksi bersamaan kedua sudah memegang kunci SHARED, itu menyebabkan kebuntuan.
Merujuk ke: https://blog.tekenlight.com/2019/02/21/database-deadlock-mysql.html
sumber
Untuk programmer Java yang menggunakan Spring, saya menghindari masalah ini menggunakan aspek AOP yang secara otomatis mencoba kembali transaksi yang mengalami kebuntuan sementara.
Lihat @RetryTransaction Javadoc untuk info lebih lanjut.
sumber
Saya punya metode, bagian dalamnya dibungkus dengan MySqlTransaction.
Masalah kebuntuan muncul untuk saya ketika saya menjalankan metode yang sama secara paralel dengan dirinya sendiri.
Tidak ada masalah menjalankan instance dari metode ini.
Ketika saya menghapus MySqlTransaction, saya dapat menjalankan metode secara paralel dengan dirinya sendiri tanpa masalah.
Hanya berbagi pengalaman, saya tidak menganjurkan apa pun.
sumber
cron
berbahaya. Jika salah satu instance cron gagal selesai sebelum yang berikutnya jatuh tempo, mereka cenderung saling bertarung.Akan lebih baik untuk memiliki pekerjaan yang terus berjalan yang akan menghapus beberapa baris, tidur beberapa, lalu ulangi.
Juga,
INDEX(datetime)
sangat penting untuk menghindari kebuntuan.Tapi, jika tes datetime mencakup lebih dari, katakanlah, 20% dari tabel,
DELETE
akan melakukan pemindaian tabel. Potongan kecil yang dihapus lebih sering adalah solusi.Alasan lain untuk menggunakan potongan yang lebih kecil adalah untuk mengunci lebih sedikit baris.
Intinya:
INDEX(datetime)
Teknik penghapusan lainnya: http://mysql.rjweb.org/doc.php/deletebig
sumber