Як налагодити перевищення часу очікування Lock на MySQL?


269

У своїх журналах помилок виробництва я періодично бачу:

SQLSTATE [HY000]: Загальна помилка: 1205 перервано час очікування блокування; спробуйте перезапустити транзакцію

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



Відповіді:


261

Це віддає це слово транзакції . Очевидно з твердження, що запит намагався змінити принаймні один рядок в одній або декількох таблицях InnoDB.

Оскільки ви знаєте запит, всі таблиці, до яких звертається, є винуватцями.

Звідти ви повинні мати можливість бігати SHOW ENGINE INNODB STATUS\G

Ви повинні мати змогу побачити таблицю, на яку впливає

Ви отримуєте всі види додаткової інформації про блокування та Mutex.

Ось зразок одного з моїх клієнтів:

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; 

за вашим запитом


5
Для вбудованого InnoDB innodb_lock_wait_timeoutзмінну можна встановити лише при запуску сервера. Для InnoDB Plugin його можна встановити під час запуску або змінити під час виконання, і він має як глобальне, так і значення сеансу.
Тімо Хуовінен

1
Привіт @rolandomysqldba, будь ласка , ви можете дати пропозицію мені на цей пост: stackoverflow.com/questions/18267565 / ...
Manish Sapkal

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

1
@Pacerier Кожен раз, коли mysqld перезапускається, вам потрібно запустити SET GLOBAL innodb_lock_wait_timeout = 120;знову. Якщо /etc/my.cnfє можливість, innodb_lock_wait_timeoutвстановлено для вас. Не у всіх є привілей СУПЕР змінювати його в усьому світі для всіх інших ( 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

. рядкові замки)

Мораль історії полягає в тому, що транзакція може бути активною, навіть якщо нитка спить.


2
Я не можу сказати, що ти врятував мені життя, але ти точно налаштував мій погляд на мир. Читаючи вашу відповідь, я знайшов моторошну нитку, яка активна 3260 секунд і ніде не з’являється. після вбивства всі мої проблеми були вирішені!
kommradHomer

Це була моя проблема. Спляча транзакція з часом 20 000 секунд, яка заважала затримці роботи в додатку Rails працювати належним чином. Дякую @Eirik
bigtex777

Будь-яка ідея, чому сплячу транзакцію все одно не вбивають? Можливо, чи можна встановити тайм-аут, який повинен завершити транзакцію в межах?
patrickdavey

1
Інші команди, які можуть бути корисними при пошуку блокування транзакцій: show processlist;показати вичерпний перелік процесів, які виконуються в даний час, що приємно, оскільки це скорочена версія show engine innodb status\g. Крім того, якщо ваш db знаходиться в екземплярі Amazon RDS, ви можете використовувати CALL mysql.rds_kill(<thread_id>);для вбивства потоків. Я думаю, що вона має більш високі дозволи, тому що це дозволило мені знищити більше процесів, ніж звичайний kill <thread_id>;- зауважте, вони повинні запускатися в MySQL CLI
nickang

1
У когось є джерело для цього - можливо, сторінка документації, в якій зазначається, що замки розміщуються перед фазою COMMIT? Я нічого не міг знайти, не дивлячись на цю точну проблему, і це було усунено вбивством сплячої нитки, яка тримала замки.
Ерін Шкуновер

42

Через популярність MySQL, недарма перевищено час очікування блокування Lock; спробуйте перезапустити транзакцію виняток приділяє стільки уваги SO.

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

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

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

У ПОВТОРЕННІ ЧИТАТИ кожен замок, придбаний під час транзакції, зберігається протягом тривалості транзакції.

У "ЧИТАТИ", Блоки, які не відповідають скануванню, звільняються після завершення ЗВІТУ.

...

Це означає, що в READ COMMITTED інші транзакції вільно оновлювати рядки, які вони не змогли б оновити (у REPEATABLE READ) після завершення оператора UPDATE.

Отже: чим більш обмежений рівень ізоляції ( REPEATABLE_READ, SERIALIZABLE) тим більше шанси на тупик. Це не проблема "сама по собі", це компроміс.

Ви можете отримати дуже хороші результати 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 - це нічого! Це просто чекає, оскільки не знає, що робити. Він чекає, поки перший придбаний замок перевищить час очікування (Process A Transaction 1: Locks X), тоді він розблокує Lock X, який розблокує Transaction 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 . У моєму випадку у мене було два процеси PHP, в кожному я розпочав транзакцію вручну. Цікава частина полягала в тому, що, хоч я і запустив одну транзакцію в процесі, MySQL використовував внутрішньо фактично дві окремі транзакції (у мене немає поняття, чому, можливо, деякі розробки MySQL можуть пояснити).

MySQL керує своїми власними транзакціями внутрішньо і вирішив (у моєму випадку) використовувати дві транзакції для обробки всіх запитів SQL, що надходять із процесу PHP (Процес A). Заява про те, що транзакція 1 чекає завершення транзакції 3 - це внутрішня річ MySQL. MySQL "знав", що транзакція 1 і транзакція 3 були фактично створені як частина одного запиту "транзакції" (з процесу A). Тепер вся "транзакція" була заблокована, оскільки транзакція 3 (підрозділ "транзакції") була заблокована. Оскільки "транзакція" не змогла закінчити транзакцію 1 (також підрозділ "транзакції"), також було позначено як незавершене. Це те, що я мав на увазі під "транзакцією 1 чекає завершення транзакції 3".


14

Велика проблема цього винятку полягає в тому, що його зазвичай не можна відтворити в тестовому середовищі, і ми не маємо змоги запускати статус двигуна innodb, коли це відбувається в prod. Так що в одному з проектів я вклав наведений нижче код у блок ловлі для цього винятку. Це допомогло мені зрозуміти стан двигуна, коли сталося виняток. Це дуже допомогло.

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

Погляньте на сторінку чоловіка pt-deadlock-loggerутиліти :

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

Він витягує інформацію із engine innodb statusзгаданого вище, а також її можна використовувати для створення, daemonяка працює кожні 30 секунд.


3
цей інструмент зараз є частиною набору інструментів Percona
Бред Мейс

Час очікування блокування очікування не такий, як тупикові місця, зокрема innodb не показує ніякої інформації про них, оскільки вони не виявлені тупиками, тому я не думаю, що pt-deadlock-реєстратор не допомагає.
Джей Паролайн

Час очікування та тупики блокування пов’язані, хоча - див. Dev.mysql.com/doc/refman/5.7/uk/innodb-deadlock-detection.html
Андрій Сура

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? Я не бачу, що відрізняє цього від інших.
Водін

Ні, це, мабуть, не той потік ID, це був приклад. Ви повинні знайти ідентифікатори потоку з команди "show engine innodb status \ G" та розібратися, який з них блокує базу даних.
Еллерт ван Коперен

1
Дякую. Отже, іншими словами, немає способу сказати, хто саме, наприклад, вбиваючи їх по черзі?
Водін

У списку транзакцій ви можете побачити, які з них виконуються та на який термін. Тож не потрібно вбивати їх один за одним, цей список зазвичай дає вам дуже гарне уявлення про те, що відбувається.
Еллерт ван Коперен

10

Ось, що я в кінцевому підсумку повинен був зробити, щоб зрозуміти, який "інший запит" спричинив проблему очікування блокування. У коді програми ми відстежуємо всі очікувані виклики бази даних в окремому потоці, присвяченому цій задачі. Якщо будь-який дзвінок DB триває більше 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 після цього.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.