Solusi yang diterima, sayangnya, salah . Itu benar sejauh yang dikatakan,
Ini memang (hampir pasti; lihat di bawah) apa yang harus dilakukan. Tapi kemudian itu menyarankan,
...dan 1398 bukan sambungan dengan kunci. Bagaimana mungkin? 1398 adalah koneksi menunggu untuk kunci. Artinya belum ada kunci, dan karena itu, membunuhnya tidak ada gunanya. Proses menahan gembok akan tetap menahan gembok, dan berikutnya utas mencoba melakukan sesuatu karena itu akan juga berhenti dan masukkan "Menunggu kunci metadata" secara berurutan.
Anda tidak memiliki jaminan bahwa proses "menunggu kunci metadata" (WFML) juga tidak akan memblokir, tetapi Anda dapat yakin bahwa mematikan hanya proses WFML akan menghasilkan tidak ada persisnya. .
Penyebab sebenarnya adalah proses lain menahan kunci , dan yang lebih penting, SHOW FULL PROCESSLIST
tidak akan memberi tahu Anda secara langsung yang mana .
Itu AKAN memberi tahu Anda jika prosesnya berhasil sesuatu, ya. Biasanya berhasil. Di sini, proses menahan kunci tidak melakukan apa-apa , dan bersembunyi di antara utas lainnya juga tidak melakukan apa-apa.
Dalam hal ini pelakunya hampir pasti proses 1396 , yang dimulai sebelum proses 1398 dan sekarang dalam Sleep
negara, dan telah selama 46 detik. Sejak 1396 dengan jelas melakukan semua yang perlu dilakukan (sebagaimana dibuktikan oleh fakta bahwa sekarang sedang tidur, dan telah melakukannya selama 46 detik, sejauh menyangkut MySQL ), tidak ada utas yang tertidur sebelumnya yang dapat mengunci (atau 1396 juga akan terhenti).
PENTING :jika Anda terhubung ke MySQL sebagai pengguna terbatas, SHOW FULL PROCESSLIST
akan tidak menunjukkan semua proses. Jadi kuncinya mungkin dipegang oleh proses yang tidak Anda lihat.
SHOW PROCESSLIST
yang lebih baik
SELECT ID, TIME, USER, HOST, DB, COMMAND, STATE, INFO
FROM INFORMATION_SCHEMA.PROCESSLIST WHERE DB IS NOT NULL
AND (`INFO` NOT LIKE '%INFORMATION_SCHEMA%' OR INFO IS NULL)
ORDER BY `DB`, `TIME` DESC
Di atas dapat disetel untuk hanya menampilkan proses dalam status SLEEP, dan bagaimanapun itu akan mengurutkannya berdasarkan waktu turun, sehingga lebih mudah untuk menemukan proses yang menggantung (biasanya adalah Sleep
'ing satu segera sebelum yang "menunggu kunci metadata").
Yang penting
Biarkan proses "menunggu kunci metadata" sendirian .
Solusi cepat dan kotor, tidak terlalu direkomendasikan tapi cepat
Bunuh semua proses dalam status "Tidur", pada database yang sama, yang lebih tua dari yang terlama utas dalam status "menunggu kunci metadata". Inilah yang Arnaud Amaury akan dilakukan:
- untuk setiap database yang memiliki setidaknya satu thread di WaitingForMetadataLock:
- koneksi tertua di WFML pada DB itu ternyata berumur Z detik
- SEMUA utas "Tidur" pada DB itu dan yang lebih lama dari Z harus pergi. Mulailah dengan yang paling segar, untuk berjaga-jaga.
- Jika ada satu koneksi lama dan non-tidur pada DB itu, maka mungkin itu yang memegang kunci, tetapi melakukan sesuatu . Anda tentu saja dapat membunuhnya, tetapi terutama jika itu adalah UPDATE/INSERT/DELETE, Anda melakukannya dengan risiko Anda sendiri.
Sembilan puluh sembilan kali dari seratus, utas yang akan dibunuh adalah yang paling muda di antara mereka yang dalam keadaan Tidur yang lebih tua daripada yang lama menunggu kunci metadata:
TIME STATUS
319 Sleep
205 Sleep
19 Sleep <--- one of these two "19"
19 Sleep <--- and probably this one(*)
15 Waiting for metadata lock <--- oldest WFML
15 Waiting for metadata lock
14 Waiting for metadata lock
(*) urutan TIME sebenarnya memiliki milidetik, atau jadi saya diberitahu, itu tidak menunjukkannya. Jadi sementara kedua proses memiliki nilai Waktu 19, yang terendah harus lebih muda.
Perbaikan yang lebih fokus
Jalankan SHOW ENGINE INNODB STATUS
dan lihat bagian "TRANSAKSI". Anda akan menemukan, antara lain, sesuatu seperti
TRANSACTION 1701, ACTIVE 58 sec;2 lock struct(s), heap size 376, 1 row lock(s), undo log entries 1
MySQL thread id 1396, OS thread handle 0x7fd06d675700, query id 1138 hostname 1.2.3.4 whatever;
Sekarang Anda memeriksa dengan SHOW FULL PROCESSLIST
apa yang dilakukan thread id 1396 dengan transaksi #1701-nya. Kemungkinan dalam status "Tidur". Jadi:transaksi aktif (#1701) dengan kunci aktif, bahkan telah melakukan beberapa perubahan karena memiliki entri log undo... tetapi saat ini tidak digunakan. Itu dan tidak ada yang lain adalah utas yang perlu Anda bunuh. Kehilangan perubahan itu.
Ingat bahwa tidak melakukan apa-apa di MySQL tidak berarti tidak melakukan apa-apa secara umum. Jika Anda mendapatkan beberapa catatan dari MySQL dan membuat CSV untuk unggahan FTP, selama unggahan FTP, koneksi MySQL tidak aktif.
Sebenarnya jika proses menggunakan MySQL dan server MySQL berada di mesin yang sama, mesin itu menjalankan Linux, dan Anda memiliki hak akses root, ada cara untuk mengetahui proses mana memiliki koneksi yang meminta kunci. Ini pada gilirannya memungkinkan untuk menentukan (dari penggunaan CPU atau, paling buruk, strace -ff -p pid
) apakah proses itu benar-benar melakukan sesuatu atau tidak, untuk membantu memutuskan apakah membunuh itu aman.
Mengapa ini terjadi?
Saya melihat ini terjadi pada aplikasi web yang menggunakan koneksi MySQL "persisten" atau "kumpulan", yang saat ini biasanya menghemat sedikit waktu:instance webapp dihentikan, tetapi koneksi tidak , jadi kuncinya masih hidup... dan memblokir semua orang.
Cara menarik lainnya yang saya temukan adalah, dalam hipotesis di atas, untuk menjalankan kueri yang mengembalikan beberapa baris, dan hanya mengambil beberapa di antaranya . Jika kueri tidak disetel ke "pembersihan otomatis" (namun DBA yang mendasari melakukannya), itu akan membuat koneksi tetap terbuka dan mencegah kunci penuh pada tabel masuk. Saya mengalami ini dalam sepotong kode yang memverifikasi apakah ada baris dengan memilih baris itu dan memverifikasi apakah ada kesalahan (tidak ada) atau tidak (harus ada), tetapi tanpa benar-benar mengambil baris .
Tanyakan ke DB
Cara lain untuk mendapatkan pelakunya jika Anda memiliki MySQL baru-baru ini, tetapi tidak terlalu baru karena ini tidak akan digunakan lagi , adalah (Anda memerlukan hak istimewa lagi pada skema informasi)
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS
WHERE LOCK_TRX_ID IN
(SELECT BLOCKING_TRX_ID FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS);
Solusi aktual, membutuhkan waktu dan kerja
Masalahnya biasanya disebabkan oleh arsitektur ini:
Saat webapp mati, atau instance thread ringan webapp mati, kolam penampung/koneksi mungkin tidak . Dan itu adalah wadah yang membuat koneksi tetap terbuka, jadi jelas koneksi tidak menutup. Dapat diduga, MySQL tidak menganggap operasi selesai .
Jika aplikasi web tidak membersihkan dirinya sendiri (tidak ada ROLLBACK
atau COMMIT
untuk transaksi, tidak ada UNLOCK TABLES
, dll.), maka apa pun yang mulai dilakukan aplikasi web itu masih ada , dan mungkin masih memblokir orang lain.
Kemudian ada dua solusi. Yang lebih buruk adalah menurunkan waktu tunggu idle
. Tapi coba tebak apa yang terjadi jika Anda menunggu terlalu lama di antara dua kueri (tepatnya:"server MySQL telah hilang"). Anda kemudian dapat menggunakan mysql_ping
jika tersedia (segera dihentikan. Ada solusi
untuk PDO. Atau Anda dapat memeriksa itu kesalahan, dan buka kembali koneksi jika itu terjadi (ini adalah cara Python). Jadi - dengan sedikit biaya kinerja - itu bisa dilakukan.
Solusi yang lebih baik dan lebih cerdas kurang mudah diterapkan. Usahakan agar skrip tetap bersih, memastikan untuk mengambil semua baris atau membebaskan semua sumber daya kueri, menangkap semua pengecualian dan menanganinya dengan benar, atau, jika mungkin, lewati koneksi persisten sama sekali . Biarkan setiap instance membuat koneksinya sendiri atau gunakan smart pengemudi kolam renang
(di PHP PDO, gunakan PDO::ATTR_PERSISTENT
secara eksplisit disetel ke false
). Alternatifnya (misalnya dalam PHP) Anda dapat meminta destruct dan exception handler membersihkan koneksi dengan melakukan atau memutar kembali transaksi dan mengeluarkan buka kunci tabel eksplisit.
Saya tidak tahu cara menanyakan sumber daya hasil yang masih ada untuk membebaskannya; satu-satunya cara adalah menyimpan sumber daya tersebut dalam larik pribadi.