Как отладить превышение времени ожидания блокировки на MySQL?

269

В моих производственных журналах ошибок я иногда вижу:

SQLSTATE [HY000]: общая ошибка: 1205 Превышено время ожидания блокировки; попробуйте перезапустить транзакцию

Я знаю, какой запрос пытается получить доступ к базе данных в данный момент, но есть ли способ узнать, какой запрос был заблокирован в тот момент?

Мэтт Маккормик
источник
1
Я настоятельно рекомендую всем дать ответ Эйрика
kommradHomer
Возможный дубликат Как преодолеть
Макс Александр Ханна

Ответы:

261

То, что выдает это, является словом транзакция . Из заявления видно, что запрос пытался изменить хотя бы одну строку в одной или нескольких таблицах InnoDB.

Поскольку вы знаете запрос, все таблицы, к которым осуществляется доступ, являются кандидатами на то, чтобы быть виновником.

Оттуда вы должны быть в состоянии бежать SHOW ENGINE INNODB STATUS\G

Вы должны быть в состоянии увидеть затронутые таблицы

Вы получаете все виды дополнительной информации о блокировке и мьютексе.

Вот образец от одного из моих клиентов:

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)

Вам следует рассмотреть возможность увеличения значения тайм-аута ожидания блокировки для InnoDB, установив innodb_lock_wait_timeout , по умолчанию 50 секунд

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

Вы можете установить его на более высокое значение /etc/my.cnfпостоянно с этой линией

[mysqld]
innodb_lock_wait_timeout=120

и перезапустите MySQL. Если вы не можете перезапустить MySQL в это время, запустите это:

SET GLOBAL innodb_lock_wait_timeout = 120; 

Вы также можете просто установить его на время вашей сессии

SET innodb_lock_wait_timeout = 120; 

с последующим вашим запросом

RolandoMySQLDBA
источник
5
Для встроенного InnoDB innodb_lock_wait_timeoutпеременная может быть установлена ​​только при запуске сервера. Для плагина InnoDB он может быть установлен при запуске или изменен во время выполнения и имеет как глобальные значения, так и значения сеанса.
Тимо Хуовинен
1
Привет @rolandomysqldba, не могли бы вы дать мне предложение на этот пост: stackoverflow.com/questions/18267565/…
Маниш Сапкал
2
Я получаю эту ошибку при попытке выполнить первый запрос: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
Юлиан Онофрей
1
@Pacerier Каждый раз, когда mysqld перезапускается, вы должны SET GLOBAL innodb_lock_wait_timeout = 120;снова запускаться . Если /etc/my.cnfесть опция, innodb_lock_wait_timeoutустанавливается для вас. Не у всех есть привилегия SUPER, чтобы изменить это глобально для всех остальных ( dev.mysql.com/doc/refman/5.6/en/… )
RolandoMySQLDBA
3
@IulianOnofrei Символ \ G - это особенность командной строки MySQL, которая изменяет способ отображения вывода. Для других клиентов MySQL просто используйте обычную точку с запятой.
thenickdude
83

Как кто-то упомянул в одной из многих SO-тем, касающихся этой проблемы: иногда процесс, который заблокировал таблицу, отображается как спящий в списке процессов! Я вырывал свои волосы, пока не убил все спящие потоки, которые были открыты в рассматриваемой базе данных (ни один не был активен в то время). Это, наконец, разблокировало таблицу и позволило выполнить запрос на обновление.

Комментатор сказал что-то вроде: «Иногда поток MySQL блокирует таблицу, а затем спит, ожидая, когда произойдет нечто, не связанное с MySQL».

После повторного просмотра show engine innodb statusжурнала (после того, как я разыскал клиента, ответственного за блокировку), я заметил, что рассматриваемый застрявший поток был указан в самом низу списка транзакций, под активными запросами, которые собирались вызвать ошибку из-за замороженного замка:

------------------
---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

(не уверен, что сообщение «Просмотр чтения Trx» связано с заблокированной блокировкой, но в отличие от других активных транзакций, эта не отображается с выполненным запросом и вместо этого утверждает, что транзакция «очищается», но имеет несколько рядные замки)

Мораль этой истории заключается в том, что транзакция может быть активной, даже если поток спит.

Эрик Л.
источник
2
Я не могу сказать, что вы спасли мою жизнь, но вы наверняка помирились. Читая ваш ответ, я нашел жуткую ветку, которая активна 3260 секунд и нигде не появляется. после убийства все мои проблемы были решены!
kommradHomer
Это была моя проблема. Спящая транзакция со временем 20000 секунд, которая препятствовала правильной работе отложенного задания в приложении Rails. Спасибо @Eirik
bigtex777
Есть идеи, почему спящая транзакция не может быть уничтожена? Например, есть ли тайм-аут, который вы можете установить для завершения транзакции?
Patrickdavey
1
Другие команды, которые могут быть полезны при поиске блокирующих транзакций: show processlist;показать исчерпывающий список процессов, выполняемых в настоящее время, что приятно, потому что это сжатая версия show engine innodb status\g. Кроме того, если ваша база данных находится на экземпляре Amazon RDS, вы можете использовать ее CALL mysql.rds_kill(<thread_id>);для уничтожения потоков. Я думаю, что у него более высокие разрешения, потому что он позволил мне убить больше процессов, чем просто kill <thread_id>;- обратите внимание, что они должны выполняться в MySQL CLI
nickang
1
У кого-нибудь есть источник для этого - может быть, страница документации, в которой говорится о блокировках перед фазой COMMIT? Я ничего не мог найти, несмотря на то, что увидел именно эту проблему, и она была прояснена, убив спящую нить, которая держала замки.
Эрин Шуновер
42

Из-за популярности MySQL неудивительно, что превышено время ожидания блокировки; попробуйте перезапустить исключение транзакции получает столько внимания на SO.

Чем больше у вас разногласий, тем больше вероятность возникновения взаимоблокировок, которые механизм БД разрешит путем тайм-аута одной из взаимоблокированных транзакций. Кроме того, долгосрочные транзакции, которые изменили (например, UPDATEили DELETE) большое количество записей (которые принимают блокировки, чтобы избежать аномалий грязной записи, как объяснено в книге High-Performance Java Persistence ), с большей вероятностью вызывают конфликты с другими транзакциями.

Хотя InnoDB MVCC, вы все равно можете запросить явные блокировки, используя FOR UPDATEпредложение . Однако, в отличие от других популярных БД (Oracle, MSSQL, PostgreSQL, DB2), MySQL использует REPEATABLE_READуровень изоляции по умолчанию .

Теперь полученные вами блокировки (либо путем изменения строк, либо с помощью явной блокировки) сохраняются на время текущей выполняемой транзакции. Если вы хотите получить хорошее объяснение различий между блокировками REPEATABLE_READи READ COMMITTEDв отношении блокировки, прочитайте эту статью о Percona .

В REPEATABLE READ каждая блокировка, полученная во время транзакции, удерживается на протяжении транзакции.

В READ COMMITTED блокировки, не соответствующие сканированию, снимаются после завершения ЗАЯВЛЕНИЯ.

...

Это означает, что в READ COMMITTED другие транзакции могут свободно обновлять строки, которые они не смогли бы обновить (в REPEATABLE READ) после завершения оператора UPDATE.

Следовательно: чем более ограничивающий уровень изоляции ( REPEATABLE_READ, SERIALIZABLE), тем больше вероятность тупика. Это не проблема "per se", это компромисс.

Вы можете получить очень хорошие результаты READ_COMMITED, поскольку вам нужно предотвращать потерю обновлений на уровне приложений при использовании логических транзакций, которые охватывают несколько HTTP-запросов. В оптимистической блокировках цель подхода потеряла обновление , которые могут произойти , даже если вы используете SERIALIZABLEуровень изоляции при одновременном снижении раздора блокировки, позволяя использовать READ_COMMITED.

Влад Михалча
источник
4
Разве время ожидания блокировки не отличается от тупика? Например, если по законным причинам один поток удерживает блокировку в течение 60 секунд, может возникнуть тайм-аут ожидания блокировки. Не правда ли, что, если действительно существует тупик, MySQL обнаружит это и мгновенно завершит транзакцию, и это не связано с тайм-аутом ожидания блокировки?
ColinM
1
Ты прав. БД обнаруживает мертвую блокировку по истечении времени ожидания и убивает один ожидающий процесс, поэтому одна транзакция выигрывает, а другая завершается неудачей. Но чем дольше вы удерживаете блокировку, тем меньше масштабируется приложение. Даже если вы не столкнетесь с тупиковыми блокировками, вы все равно увеличите сериализуемую часть поведения вашего приложения во время выполнения.
Влад Михалча,
19

Для записи, исключение тайм-аута ожидания блокировки также происходит, когда есть тупик, и MySQL не может обнаружить его, так что это просто время ожидания. Другой причиной может быть чрезвычайно длительный запрос, который проще решить / исправить, однако я не буду описывать этот случай здесь.

MySQL обычно может справиться с взаимоблокировками, если они построены «правильно» в течение двух транзакций. Затем MySQL просто убивает / откатывает одну транзакцию, которая владеет меньшим количеством блокировок (менее важно, так как это повлияет на меньшее количество строк), и позволяет другой завершиться.

Теперь предположим, что есть два процесса A и B и 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 

Это очень неудачная установка, потому что MySQL не может видеть, что есть тупик (охватываемый в 3 транзакциях). Так что MySQL делает ... ничего! Он просто ждет, так как не знает, что делать. Он ожидает, пока первая полученная блокировка не превысит время ожидания (Процесс A, транзакция 1: блокировки X), затем разблокирует блокировку X, которая разблокирует транзакцию 2 и т. Д.

Задача состоит в том, чтобы выяснить, что (какой запрос) вызывает первую блокировку (Lock X). Вы сможете легко увидеть ( show engine innodb status), что транзакция 3 ожидает транзакцию 2, но вы не увидите, какую транзакцию ожидает транзакция 2 (транзакция 1). MySQL не будет печатать какие-либо блокировки или запросы, связанные с транзакцией 1. Единственным указанием будет то, что в самом низу списка транзакций ( show engine innodb statusраспечатки) вы увидите, что транзакция 1, по-видимому, ничего не делает (но на самом деле ожидает транзакции 3, чтобы финиш).

Методика определения того, какой SQL-запрос вызывает блокировку (Lock X) для данной транзакции, которая ожидает, описана здесь Tracking MySQL query history in long running transactions

Если вам интересно, что за процесс и транзакция именно в примере. Процесс является процессом PHP. Транзакция - это транзакция, определенная innodb-trx-table . В моем случае у меня было два PHP-процесса, в каждом из которых я запускал транзакцию вручную. Интересно то, что, хотя я и начал одну транзакцию в процессе, MySQL фактически использовал две отдельные транзакции (я понятия не имею, почему, может быть, некоторые разработчики MySQL могут это объяснить).

MySQL внутренне управляет своими собственными транзакциями и решила (в моем случае) использовать две транзакции для обработки всех запросов SQL, поступающих от процесса PHP (Процесс A). Утверждение о том, что транзакция 1 ожидает завершения транзакции 3, является внутренней вещью MySQL. MySQL «знал», что транзакция 1 и транзакция 3 фактически были созданы как часть одного запроса «транзакции» (из процесса A). Теперь вся «транзакция» была заблокирована, потому что транзакция 3 (часть «транзакции») была заблокирована. Поскольку «транзакция» не смогла завершить транзакцию 1 (также часть «транзакции») была помечена как не завершенная. Это то, что я имел в виду под «Транзакцией 1, ожидающей завершения Транзакции 3».

Томас Билка
источник
14

Большая проблема с этим исключением состоит в том, что его обычно невозможно воспроизвести в тестовой среде, и мы не собираемся запускать состояние движка innodb, когда это происходит на prod. Поэтому в одном из проектов я поместил приведенный ниже код в блок catch для этого исключения. Это помогло мне поймать состояние двигателя, когда произошло исключение. Это очень помогло.

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));
}
Maruthi
источник
11

Взгляните на справочную страницу pt-deadlock-loggerутилиты :

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

Он извлекает информацию из engine innodb statusупомянутого выше, а также может быть использован для создания daemonкаждые 30 секунд.

Андрей Сура
источник
3
этот инструмент теперь является частью набора инструментов Percona
Брэд Мейс
Тайм-ауты ожидания блокировки не совпадают с взаимоблокировками, в частности, innodb не показывает никакой информации о них, потому что они не обнаружены взаимоблокировки, поэтому я не думаю, что pt-deadlock-logger поможет.
Джей Паролайн
Андрей Сура,
11

Экстраполируя ответ Роландо выше, именно они блокируют ваш запрос:

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

Если вам нужно выполнить ваш запрос и не можете дождаться запуска других, убейте их, используя идентификатор потока MySQL:

kill 5341773 <replace with your thread id>

(изнутри mysql, а не оболочка, очевидно)

Вы должны найти идентификаторы потоков из:

show engine innodb status\G

команда и выяснить, какой из них блокирует базу данных.

Эллерт ван Коперен
источник
1
Откуда ты это знаешь 5341773? Я не вижу, что отличает это одно от других.
Водин
Нет, это, вероятно, не тот идентификатор потока, это был пример. Вы должны найти идентификаторы потоков из команды «show engine innodb status \ G» и выяснить, какой из них блокирует базу данных.
Эллерт ван Коперен
1
Спасибо. Другими словами, невозможно сказать, кто это, например, не убивая их одного за другим?
Водин
В списке транзакций вы можете увидеть, какие из них выполняются и как долго. Так что нет необходимости убивать их одного за другим, этот список обычно дает вам довольно хорошее представление о том, что происходит.
Эллерт ван Коперен
10

Вот что мне в конечном итоге пришлось сделать, чтобы выяснить, какой «другой запрос» вызвал проблему тайм-аута блокировки. В коде приложения мы отслеживаем все ожидающие обращения к базе данных в отдельном потоке, посвященном этой задаче. Если какой-либо вызов БД занимает больше N секунд (для нас это 30 секунд), мы регистрируем:

-- 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;

С учетом вышеизложенного мы смогли точно определить параллельные запросы, которые блокировали строки, вызывая взаимоблокировку. В моем случае это были операторы, подобные INSERT ... SELECTкоторым в отличие от простых команд SELECT блокируют основные строки. Затем вы можете реорганизовать код или использовать другую изоляцию транзакции, например, чтение без фиксации.

Удачи!

Славомир
источник
9

Ты можешь использовать:

show full processlist

который перечислит все соединения в MySQL и текущее состояние соединения, а также выполняемый запрос. Есть также более короткий вариант, show processlist;который отображает усеченный запрос, а также статистику соединения.

Геррит Бринк
источник
-2

Активируйте MySQL general.log (интенсивное использование диска) и используйте mysql_analyse_general_log.pl для извлечения длительных транзакций, например с:

--min-duration = ваше значение innodb_lock_wait_timeout

Отключите general.log после этого.

Mick
источник