pengembangan-web-mp.com

Bagaimana cara debug Batas waktu tunggu tunggu terlampaui di MySQL?

Dalam log kesalahan produksi, saya sesekali melihat:

SQLSTATE [HY000]: Kesalahan umum: 1205 Batas waktu tunggu tunggu melebihi; mencoba memulai 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?

226
Matt McCormick

Apa yang memberi ini adalah Word transaksi. Hal ini terbukti 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 dapat menjalankan 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:

mysql> show engine innodb status\G
*************************** 1. row ***************************
  Type: InnoDB
  Name:
Status:
=====================================
110514 19:44:14 INNODB MONITOR OUTPUT
=====================================
Per second averages calculated from the last 4 seconds
----------
SEMAPHORES
----------
OS WAIT ARRAY INFO: reservation count 9014315, signal count 7805377
Mutex spin waits 0, rounds 11487096053, OS waits 7756855
RW-shared spins 722142, OS waits 211221; RW-excl spins 787046, OS waits 39353
------------------------
LATEST FOREIGN KEY ERROR
------------------------
110507 21:41:35 Transaction:
TRANSACTION 0 606162814, ACTIVE 0 sec, process no 29956, OS thread id 1223895360 updating or deleting, thread declared inside InnoDB 499
mysql tables in use 1, locked 1
14 lock struct(s), heap size 3024, 8 row lock(s), undo log entries 1
MySQL thread id 3686635, query id 124164167 10.64.89.145 viget updating
DELETE FROM file WHERE file_id in ('6dbafa39-7f00-0001-51f2-412a450be5cc' )
Foreign key constraint fails for table `backoffice`.`attachment`:
,
  CONSTRAINT `attachment_ibfk_2` FOREIGN KEY (`file_id`) REFERENCES `file` (`file_id`)
Trying to delete or update in parent table, in index `PRIMARY` Tuple:
DATA Tuple: 17 fields;
 0: len 36; hex 36646261666133392d376630302d303030312d353166322d343132613435306265356363; asc 6dbafa39-7f00-0001-51f2-412a450be5cc;; 1: len 6; hex 000024214f7e; asc   $!O~;; 2: len 7; hex 000000400217bc; asc    @   ;; 3: len 2; hex 03e9; asc   ;; 4: len 2; hex 03e8; asc   ;; 5: len 36; hex 65666635323863622d376630302d303030312d336632662d353239626433653361333032; asc eff528cb-7f00-0001-3f2f-529bd3e3a302;; 6: len 40; hex 36646234376337652d376630302d303030312d353166322d3431326132346664656366352e6d7033; asc 6db47c7e-7f00-0001-51f2-412a24fdecf5.mp3;; 7: len 21; hex 416e67656c73204e6f7720436f6e666572656e6365; asc Angels Now Conference;; 8: len 34; hex 416e67656c73204e6f7720436f6e666572656e6365204a756c7920392c2032303131; asc Angels Now Conference July 9, 2011;; 9: len 1; hex 80; asc  ;; 10: len 8; hex 8000124a5262bdf4; asc    JRb  ;; 11: len 8; hex 8000124a57669dc3; asc    JWf  ;; 12: SQL NULL; 13: len 5; hex 8000012200; asc    " ;; 14: len 1; hex 80; asc  ;; 15: len 2; hex 83e8; asc   ;; 16: len 4; hex 8000000a; asc     ;;

But in child table `backoffice`.`attachment`, in index `PRIMARY`, there is a record:
PHYSICAL RECORD: n_fields 6; compact format; info bits 0
 0: len 30; hex 36646261666133392d376630302d303030312d353166322d343132613435; asc 6dbafa39-7f00-0001-51f2-412a45;...(truncated); 1: len 30; hex 38666164663561652d376630302d303030312d326436612d636164326361; asc 8fadf5ae-7f00-0001-2d6a-cad2ca;...(truncated); 2: len 6; hex 00002297b3ff; asc   "   ;; 3: len 7; hex 80000040070110; asc    @   ;; 4: len 2; hex 0000; asc   ;; 5: len 30; hex 416e67656c73204e6f7720436f6e666572656e636520446f63756d656e74; asc Angels Now Conference Document;;

------------
TRANSACTIONS
------------
Trx id counter 0 620783814
Purge done for trx's n:o < 0 620783800 undo n:o < 0 0
History list length 35
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 0 0, not started, process no 29956, OS thread id 1192212800
MySQL thread id 5341758, query id 189708501 127.0.0.1 lwdba
show innodb status
---TRANSACTION 0 620783788, not started, process no 29956, OS thread id 1196472640
MySQL thread id 5341773, query id 189708353 10.64.89.143 viget
---TRANSACTION 0 0, not started, process no 29956, OS thread id 1223895360
MySQL thread id 5341667, query id 189706152 10.64.89.145 viget
---TRANSACTION 0 0, not started, process no 29956, OS thread id 1227888960
MySQL thread id 5341556, query id 189699857 172.16.135.63 lwdba
---TRANSACTION 0 620781112, not started, process no 29956, OS thread id 1222297920
MySQL thread id 5341511, query id 189696265 10.64.89.143 viget
---TRANSACTION 0 620783736, not started, process no 29956, OS thread id 1229752640
MySQL thread id 5339005, query id 189707998 10.64.89.144 viget
---TRANSACTION 0 620783785, not started, process no 29956, OS thread id 1198602560
MySQL thread id 5337583, query id 189708349 10.64.89.145 viget
---TRANSACTION 0 620783469, not started, process no 29956, OS thread id 1224161600
MySQL thread id 5333500, query id 189708478 10.64.89.144 viget
---TRANSACTION 0 620781240, not started, process no 29956, OS thread id 1198336320
MySQL thread id 5324256, query id 189708493 10.64.89.145 viget
---TRANSACTION 0 617458223, not started, process no 29956, OS thread id 1195141440
MySQL thread id 736, query id 175038790 Has read all relay log; waiting for the slave I/O thread to update it
--------
FILE I/O
--------
I/O thread 0 state: waiting for i/o request (insert buffer thread)
I/O thread 1 state: waiting for i/o request (log thread)
I/O thread 2 state: waiting for i/o request (read thread)
I/O thread 3 state: waiting for i/o request (write thread)
Pending normal aio reads: 0, aio writes: 0,
 ibuf aio reads: 0, log i/o's: 0, sync i/o's: 0
Pending flushes (fsync) log: 0; buffer pool: 0
519878 OS file reads, 18962880 OS file writes, 13349046 OS fsyncs
0.00 reads/s, 0 avg bytes/read, 6.25 writes/s, 4.50 fsyncs/s
-------------------------------------
INSERT BUFFER AND ADAPTIVE HASH INDEX
-------------------------------------
Ibuf: size 1, free list len 1190, seg size 1192,
174800 inserts, 174800 merged recs, 54439 merges
Hash table size 35401603, node heap has 35160 buffer(s)
0.50 hash searches/s, 11.75 non-hash searches/s
---
LOG
---
Log sequence number 28 1235093534
Log flushed up to   28 1235093534
Last checkpoint at  28 1235091275
0 pending log writes, 0 pending chkp writes
12262564 log i/o's done, 3.25 log i/o's/second
----------------------
BUFFER POOL AND MEMORY
----------------------
Total memory allocated 18909316674; in additional pool allocated 1048576
Dictionary memory allocated 2019632
Buffer pool size   1048576
Free buffers       175763
Database pages     837653
Modified db pages  6
Pending reads 0
Pending writes: LRU 0, flush list 0, single page 0
Pages read 770138, created 108485, written 7795318
0.00 reads/s, 0.00 creates/s, 4.25 writes/s
Buffer pool hit rate 1000 / 1000
--------------
ROW OPERATIONS
--------------
0 queries inside InnoDB, 0 queries in queue
1 read views open inside InnoDB
Main thread process no. 29956, id 1185823040, state: sleeping
Number of rows inserted 6453767, updated 4602534, deleted 3638793, read 388349505551
0.25 inserts/s, 1.25 updates/s, 0.00 deletes/s, 2.75 reads/s
----------------------------
END OF INNODB MONITOR OUTPUT
============================

1 row in set, 1 warning (0.00 sec)

Anda harus mempertimbangkan untuk meningkatkan nilai waktu tunggu tunggu kunci untuk InnoDB dengan mengatur innodb_lock_wait_timeout , standarnya adalah 50 detik

mysql> show variables like 'innodb_lock_wait_timeout';
+--------------------------+-------+
| Variable_name            | Value |
+--------------------------+-------+
| innodb_lock_wait_timeout | 50    |
+--------------------------+-------+
1 row in set (0.01 sec)

Anda dapat mengaturnya ke nilai yang lebih tinggi di /etc/my.cnf secara permanen dengan baris ini

[mysqld]
innodb_lock_wait_timeout=120

dan mulai kembali mysql. Jika Anda tidak dapat memulai ulang mysql saat ini, jalankan ini:

SET GLOBAL innodb_lock_wait_timeout = 120; 

Anda juga bisa mengaturnya selama durasi sesi Anda

SET innodb_lock_wait_timeout = 120; 

diikuti oleh kueri Anda

232
RolandoMySQLDBA

Seperti seseorang yang disebutkan dalam salah satu dari banyak SO utas mengenai masalah ini: Kadang-kadang proses yang telah mengunci tabel muncul sebagai tidur di daftar proses! Saya merobek rambut saya sampai saya membunuh semua utas tidur yang terbuka di database yang bersangkutan (tidak ada yang aktif pada saat itu). Itu 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 log show engine innodb status (setelah saya melacak klien yang bertanggung jawab untuk kunci), saya perhatikan bahwa utas yang macet itu tercantum di bagian paling bawah daftar transaksi, di bawah kueri aktif yang akan kesalahan karena kunci beku:

------------------
---TRANSACTION 2744943820, ACTIVE 1154 sec(!!)
2 lock struct(s), heap size 376, 2 row lock(s), undo log entries 1
MySQL thread id 276558, OS thread handle 0x7f93762e7710, query id 59264109 [ip] [database] cleaning up
Trx read view will not see trx with id >= 2744943821, sees < 2744943821

(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 thread sedang tidur.

71
Eric Lawler

Karena popularitas MySQL, tidak heran Kunci waktu tunggu tunggu terlampaui; coba mulai kembali transaksi pengecualian mendapat 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 (mis. UPDATE atau DELETE) sejumlah besar entri (yang mengambil kunci untuk menghindari anomali tulis-kotor seperti yang dijelaskan dalam buku Java Persistensi Tinggi buku) lebih cenderung menghasilkan konflik dengan transaksi lain.

Meskipun InnoDB MVCC, Anda masih dapat meminta kunci eksplisit menggunakan klausa FOR UPDATE . Namun, tidak seperti DB populer lainnya (Oracle, MSSQL, PostgreSQL, DB2), MySQL menggunakan REPEATABLE_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 dan READ COMMITTED dalam hal penguncian, silakan baca artikel Percona ini .

Dalam REPEATABLE READ setiap kunci yang diperoleh selama suatu transaksi dipegang selama durasi transaksi.

Dalam READ COMMITTED, kunci yang tidak cocok dengan pemindaian dilepaskan setelah PERNYATAAN selesai.

...

Ini berarti bahwa dalam READ COMMITTED, transaksi lainnya bebas untuk memperbarui baris yang tidak akan dapat mereka perbarui (dalam REPEATABLE READ) setelah pernyataan UPDATE selesai.

Ini semakin membatasi 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 dengan READ_COMMITED, karena Anda memerlukan pencegahan pembaruan tingkat hilang aplikasi ketika menggunakan transaksi logis yang menjangkau beberapa permintaan HTTP. penguncian optimistis target pendekatan pembaruan yang hilang yang mungkin terjadi bahkan jika Anda menggunakan tingkat isolasi SERIALIZABLE sambil mengurangi pertentangan kunci dengan memungkinkan Anda menggunakan READ_COMMITED.

31
Vlad Mihalcea

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 diselesaikan/diperbaiki, 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, anggaplah ada dua proses transaksi A dan B dan 3:

Process A Transaction 1: Locks X
Process B Transaction 2: Locks Y
Process A Transaction 3: Needs Y => Waits for Y
Process B Transaction 2: Needs X => Waits for X
Process A Transaction 1: Waits for Transaction 3 to finish

(see the last two paragraph below to specify the terms in more detail)

=> deadlock 

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 melihat dengan mudah (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 (dari cetakan show engine innodb status), Anda akan melihat Transaksi 1 tampaknya tidak melakukan apa-apa (tetapi sebenarnya menunggu Transaksi 3) menyelesaikan).

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 walaupun 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".

18
Tomas Bilka

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.

Statement st = con.createStatement();
ResultSet rs =  st.executeQuery("SHOW ENGINE INNODB STATUS");
while(rs.next()){
    log.info(rs.getString(1));
    log.info(rs.getString(2));
    log.info(rs.getString(3));
}
11
Maruthi

Lihatlah halaman manual utilitas pt-deadlock-logger :

brew install percona-toolkit
pt-deadlock-logger --ask-pass server_name

Ini mengekstrak informasi dari engine innodb status yang disebutkan di atas dan juga Dapat digunakan untuk membuat daemon yang berjalan setiap 30 detik.

9
Andrei Sura

Mengekstrapolasi dari jawaban Rolando di atas, inilah yang memblokir permintaan Anda:

---TRANSACTION 0 620783788, not started, process no 29956, OS thread id 1196472640
MySQL thread id 5341773, query id 189708353 10.64.89.143 viget

Jika Anda perlu mengeksekusi kueri Anda dan tidak bisa menunggu yang lain berjalan, bunuh mereka menggunakan id thread MySQL:

kill 5341773

(dari dalam mysql, bukan Shell, jelas)

Anda harus menemukan ID utas dari:

show engine innodb status\G

perintah, dan mencari tahu yang mana yang memblokir database.

7

Kamu bisa memakai:

show full processlist

yang akan mencantumkan semua koneksi di MySQL dan status koneksi saat ini serta kueri yang dieksekusi. Ada juga varian lebih pendek show processlist; yang menampilkan kueri terpotong serta statistik koneksi.

6
Gerrit Brink

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:

// Pending InnoDB transactions
SELECT * FROM information_schema.innodb_trx ORDER BY trx_started; 

// Optionally, log what transaction holds what locks
SELECT * FROM information_schema.innodb_locks;

Dengan di atas, kami dapat menentukan kueri bersamaan yang mengunci baris yang menyebabkan kebuntuan. Dalam kasus saya, itu adalah pernyataan seperti 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!

2
Slawomir

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

0
th3sly