Dalam log kesalahan produksi, saya sesekali melihat:
SQLSTATE [HY000]: Kesalahan umum: 1205 Waktu tunggu tunggu kunci terlampaui; coba mulai kembali transaksi
Saya tahu permintaan mana yang mencoba mengakses database pada saat itu tetapi apakah ada cara untuk mengetahui permintaan mana yang memiliki kunci pada saat itu?
Jawaban:
Apa yang memberi ini adalah kata transaksi . Jelas dengan pernyataan bahwa kueri berusaha mengubah setidaknya satu baris dalam satu atau lebih tabel InnoDB.
Karena Anda tahu kueri, semua tabel yang diakses adalah kandidat untuk menjadi pelakunya.
Dari sana, Anda harus bisa berlari
SHOW ENGINE INNODB STATUS\G
Anda harus dapat melihat tabel yang terpengaruh
Anda mendapatkan semua jenis Informasi Penguncian dan Mutex tambahan.
Berikut ini contoh dari salah satu klien saya:
Anda harus mempertimbangkan untuk meningkatkan nilai waktu tunggu tunggu kunci untuk InnoDB dengan mengatur innodb_lock_wait_timeout , standarnya adalah 50 detik
Anda dapat mengaturnya ke nilai yang lebih tinggi
/etc/my.cnf
secara permanen dengan baris inidan mulai kembali mysql. Jika Anda tidak dapat memulai ulang mysql saat ini, jalankan ini:
Anda juga bisa mengaturnya selama durasi sesi Anda
diikuti oleh kueri Anda
sumber
innodb_lock_wait_timeout
variabel hanya dapat ditetapkan saat startup server. Untuk Plugin InnoDB, dapat diatur saat startup atau diubah saat runtime, dan memiliki nilai global dan sesi.SQL Error (1064): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '\G' at line 1
SET GLOBAL innodb_lock_wait_timeout = 120;
lagi. Jika/etc/my.cnf
memiliki opsi,innodb_lock_wait_timeout
diatur untuk Anda. Tidak semua orang memiliki hak SUPER untuk mengubahnya secara global untuk orang lain ( dev.mysql.com/doc/refman/5.6/en/… )Seperti seseorang yang disebutkan dalam salah satu dari banyak utas SO mengenai masalah ini: Kadang-kadang proses yang telah mengunci tabel muncul sebagai tidur di daftar proses! Saya mencabut rambut saya sampai saya membunuh semua utas tidur yang terbuka di database yang dimaksud (tidak ada yang aktif saat itu). Yang akhirnya membuka kunci tabel dan membiarkan kueri pembaruan berjalan.
Komentator mengatakan sesuatu yang mirip dengan "Kadang-kadang utas MySQL mengunci tabel, lalu tidur ketika menunggu sesuatu yang tidak berhubungan dengan MySQL terjadi."
Setelah meninjau kembali
show engine innodb status
log (setelah saya melacak klien yang bertanggung jawab untuk kunci), saya melihat utas yang macet tersebut tercantum di bagian paling bawah daftar transaksi, di bawah kueri aktif yang akan kesalahan keluar karena kunci beku:(tidak yakin apakah pesan "Trx read view" terkait dengan kunci yang dibekukan, tetapi tidak seperti transaksi aktif lainnya, yang ini tidak muncul dengan kueri yang dikeluarkan dan sebaliknya mengklaim transaksi tersebut adalah "pembersihan," namun memiliki banyak kunci baris)
Moral dari cerita ini adalah bahwa transaksi dapat aktif meskipun utas sedang tidur.
sumber
show processlist;
untuk menampilkan daftar lengkap dari proses yang sedang dieksekusi, yang bagus karena ini adalah versi kental darishow engine innodb status\g
. Juga, jika db Anda menggunakan instance Amazon RDS, Anda dapat menggunakannyaCALL mysql.rds_kill(<thread_id>);
untuk mematikan utas. Ini memiliki izin yang lebih tinggi, saya pikir, karena memungkinkan saya untuk membunuh lebih banyak proses daripada yang biasakill <thread_id>;
- perhatikan ini harus dijalankan dalam MySQL CLIKarena popularitas MySQL, tidak ada mengherankan waktu tunggu Lock melebihi; coba mulai kembali pengecualian transaksi mendapat begitu banyak perhatian pada SO.
Semakin banyak pertengkaran yang Anda miliki, semakin besar peluang kebuntuan, yang akan diselesaikan oleh mesin DB dengan memadamkan salah satu transaksi yang menemui jalan buntu. Juga, transaksi jangka panjang yang telah memodifikasi (misalnya
UPDATE
atauDELETE
) sejumlah besar entri (yang mengambil kunci untuk menghindari anomali tulis-kotor seperti yang dijelaskan dalam buku Java Persistence Kinerja Tinggi ) lebih cenderung menimbulkan konflik dengan transaksi lainnya.Meskipun InnoDB MVCC, Anda masih dapat meminta kunci eksplisit menggunakan
FOR UPDATE
klausa . Namun, tidak seperti DB populer lainnya (Oracle, MSSQL, PostgreSQL, DB2), MySQL menggunakanREPEATABLE_READ
sebagai tingkat isolasi default .Sekarang, kunci yang Anda peroleh (baik dengan memodifikasi baris atau menggunakan penguncian eksplisit), ditahan selama durasi transaksi yang sedang berjalan. Jika Anda ingin penjelasan yang baik tentang perbedaan antara
REPEATABLE_READ
danREAD COMMITTED
dalam hal penguncian, silakan baca artikel Percona ini .Oleh karena itu: Semakin ketat tingkat isolasi (
REPEATABLE_READ
,SERIALIZABLE
) semakin besar kemungkinan kebuntuan. Ini bukan masalah "per se", ini adalah trade-off.Anda bisa mendapatkan hasil yang sangat baik
READ_COMMITED
, karena Anda memerlukan pencegahan pembaruan tingkat aplikasi yang hilang saat menggunakan transaksi logis yang menjangkau beberapa permintaan HTTP. The penguncian optimis pendekatan target hilang update yang mungkin terjadi bahkan jika Anda menggunakanSERIALIZABLE
tingkat isolasi sementara mengurangi anggapan kunci dengan memungkinkan Anda untuk menggunakanREAD_COMMITED
.sumber
Sebagai catatan, pengecualian waktu tunggu tunggu kunci terjadi juga ketika ada jalan buntu dan MySQL tidak dapat mendeteksinya, jadi waktu itu hanya habis. Alasan lain mungkin adalah permintaan yang berjalan sangat lama, yang lebih mudah untuk diselesaikan / diperbaiki, namun, dan saya tidak akan menjelaskan kasus ini di sini.
MySQL biasanya dapat menangani kebuntuan jika mereka dibangun "dengan benar" dalam dua transaksi. MySQL kemudian hanya membunuh / mengembalikan satu transaksi yang memiliki kunci lebih sedikit (kurang penting karena akan berdampak lebih sedikit pada baris) dan memungkinkan yang lainnya menyelesaikan.
Sekarang, misalkan ada dua proses transaksi A dan B dan 3:
Ini adalah setup yang sangat disayangkan karena MySQL tidak dapat melihat ada jalan buntu (membentang dalam 3 transaksi). Jadi yang dilakukan MySQL adalah ... tidak ada apa-apa! Itu hanya menunggu, karena tidak tahu harus berbuat apa. Ia menunggu hingga kunci yang diperoleh pertama melebihi batas waktu (Proses A Transaksi 1: Kunci X), maka ini akan membuka blokir Kunci X, yang membuka kunci Transaksi 2 dll.
Seni adalah untuk mencari tahu apa (permintaan mana) yang menyebabkan kunci pertama (Kunci X). Anda akan dapat dengan mudah melihat (
show engine innodb status
) bahwa Transaksi 3 menunggu Transaksi 2, tetapi Anda tidak akan melihat transaksi Transaksi 2 mana yang menunggu (Transaksi 1). MySQL tidak akan mencetak kunci atau kueri apa pun yang terkait dengan Transaksi 1. Satu-satunya petunjuk adalah bahwa di bagian paling bawah daftar transaksi (darishow engine innodb status
hasil cetak), Anda akan melihat Transaksi 1 tampaknya tidak melakukan apa-apa (tetapi sebenarnya menunggu Transaksi 3 sampai selesai).Teknik cara menemukan kueri SQL mana yang menyebabkan kunci (Kunci X) diberikan untuk transaksi tertentu yang menunggu dijelaskan di sini
Tracking MySQL query history in long running transactions
Jika Anda bertanya-tanya apa proses dan transaksinya persis dalam contoh. Prosesnya adalah proses PHP. Transaksi adalah transaksi sebagaimana didefinisikan oleh innodb-trx-table . Dalam kasus saya, saya memiliki dua proses PHP, di masing-masing saya memulai transaksi secara manual. Bagian yang menarik adalah bahwa meskipun saya memulai satu transaksi dalam suatu proses, MySQL menggunakan secara internal sebenarnya dua transaksi terpisah (saya tidak tahu mengapa, mungkin beberapa dev MySQL dapat menjelaskan).
MySQL mengelola sendiri transaksinya secara internal dan memutuskan (dalam kasus saya) untuk menggunakan dua transaksi untuk menangani semua permintaan SQL yang berasal dari proses PHP (Proses A). Pernyataan bahwa Transaksi 1 sedang menunggu Transaksi 3 selesai adalah hal internal MySQL. MySQL "tahu" Transaksi 1 dan Transaksi 3 sebenarnya dipakai sebagai bagian dari satu permintaan "transaksi" (dari Proses A). Sekarang seluruh "transaksi" diblokir karena Transaksi 3 (bagian dari "transaksi") diblokir. Karena "transaksi" tidak dapat menyelesaikan Transaksi 1 (juga bagian dari "transaksi") ditandai sebagai tidak selesai juga. Inilah yang saya maksud dengan "Transaksi 1 menunggu Transaksi 3 selesai".
sumber
Masalah besar dengan pengecualian ini adalah bahwa biasanya tidak dapat direproduksi dalam lingkungan pengujian dan kami tidak ada untuk menjalankan status mesin innodb ketika itu terjadi pada prod. Jadi di salah satu proyek saya memasukkan kode di bawah ini ke dalam catch block untuk pengecualian ini. Itu membantu saya mengetahui status mesin ketika pengecualian terjadi. Itu sangat membantu.
sumber
Lihatlah halaman manual
pt-deadlock-logger
utilitas :Ini mengekstrak informasi dari yang
engine innodb status
disebutkan di atas dan juga dapat digunakan untuk membuatdaemon
yang berjalan setiap 30 detik.sumber
Mengekstrapolasi dari jawaban Rolando di atas, inilah yang memblokir permintaan Anda:
Jika Anda perlu mengeksekusi kueri Anda dan tidak bisa menunggu yang lain berjalan, bunuh mereka menggunakan id utas MySQL:
(dari dalam mysql, bukan shell, jelas)
Anda harus menemukan ID utas dari:
perintah, dan cari tahu yang mana yang memblokir database.
sumber
5341773
? Saya tidak melihat apa yang membedakan yang satu dari yang lain.Inilah yang akhirnya harus saya lakukan untuk mencari tahu apa "permintaan lain" yang menyebabkan masalah waktu tunggu kunci. Dalam kode aplikasi, kami melacak semua panggilan basis data yang tertunda pada utas terpisah yang didedikasikan untuk tugas ini. Jika ada panggilan DB yang lebih lama dari N-detik (bagi kami itu 30 detik) kami masuk:
Dengan di atas, kami dapat menentukan kueri bersamaan yang mengunci baris yang menyebabkan kebuntuan. Dalam kasus saya, itu adalah pernyataan
INSERT ... SELECT
yang tidak seperti SELECT biasa yang mengunci baris yang mendasarinya. Anda kemudian dapat mengatur ulang kode atau menggunakan isolasi transaksi yang berbeda seperti membaca tidak berkomitmen.Semoga berhasil!
sumber
Kamu bisa memakai:
yang akan mencantumkan semua koneksi di MySQL dan status koneksi saat ini serta permintaan yang dieksekusi. Ada juga varian
show processlist;
yang lebih pendek yang menampilkan kueri terpotong serta statistik koneksi.sumber
Jika Anda menggunakan JDBC, maka Anda memiliki opsi
includeInnodbStatusInDeadlockExceptions = true
https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-reference-configuration-properties.html
sumber
Aktifkan MySQL general.log (disk intensif) dan gunakan mysql_analyse_general_log.pl untuk mengekstrak transaksi yang sudah berjalan lama, misalnya dengan:
Nonaktifkan general.log setelah itu.
sumber