SQL Server 2014 Developer — duplicate (do not use) SQL Server 2014 Developer — duplicate (do not use) SQL Server 2014 Enterprise — duplicate (do not use) SQL Server 2014 Enterprise — duplicate (do not use) SQL Server 2014 Enterprise Core — duplicate (do not use) SQL Server 2014 Enterprise Core — duplicate (do not use) SQL Server 2014 Express — duplicate (do not use) SQL Server 2014 Express — duplicate (do not use) SQL Server 2014 Standard — duplicate (do not use) SQL Server 2014 Standard — duplicate (do not use) SQL Server 2014 Web — duplicate (do not use) SQL Server 2014 Web — duplicate (do not use) SQL Server 2014 Developer — duplicate (do not use) SQL Server 2014 Developer — duplicate (do not use) SQL Server 2014 Enterprise — duplicate (do not use) SQL Server 2014 Enterprise — duplicate (do not use) SQL Server 2014 Standard — duplicate (do not use) SQL Server 2014 Standard — duplicate (do not use) Еще…Меньше
Симптомы
Предполагается, что таблицы, которая имеет также columnstore индексы в 2014 Microsoft SQL Server. При выполнении параллельного запроса, содержащего операторы внешнего соединения для таблицы внутри запроса может вызвать взаимоблокировку, и появляется следующее сообщение об ошибке:
Ошибка 1205
Транзакция (идентификатор процесса n) был взаимно универсальный объект ожидания ресурсов с другим процессом и выбран в качестве жертвы взаимоблокировки. Запустите транзакцию повторно.
Примечание. Эта проблема возникает только при максимальная степень параллелизма (MAXDOP) устанавливается на больше 1.
Решение
Сначала проблема была исправлена в следующем накопительном обновлении SQL Server.
Накопительное обновление 1 для пакета обновления 1 для SQL Server 2014 г/en-us/help/3067839
Накопительного обновления 8 для SQL Server 2014 г/en-us/help/3067836
Сведения об исправленииСуществует исправление от корпорации Майкрософт. Однако данное исправление предназначено для устранения только проблемы, описанной в этой статье. Применяйте данное исправление только в тех системах, которые имеют данную проблему.
Если исправление доступно для скачивания, имеется раздел «Пакет исправлений доступен для скачивания» в верхней части этой статьи базы знаний. Если этого раздела нет, отправьте запрос в службу технической поддержки для получения исправления.
Примечание. Если наблюдаются другие проблемы или необходимо устранить неполадки, вам может понадобиться создать отдельный запрос на обслуживание. Стандартная оплата за поддержку будет взиматься только за дополнительные вопросы и проблемы, которые не соответствуют требованиям конкретного исправления. Полный список телефонов поддержки и обслуживания клиентов корпорации Майкрософт или создать отдельный запрос на обслуживание посетите следующий веб-узел корпорации Майкрософт:
http://support.microsoft.com/contactus/?ws=supportПримечание. В форме «Пакет исправлений доступен для скачивания» отображаются языки, для которых доступно исправление. Если нужный язык не отображается, значит исправление для данного языка отсутствует.
Статус
Корпорация Майкрософт подтверждает, что это проблема продуктов Майкрософт, перечисленных в разделе «Относится к».
Нужна дополнительная помощь?
Нужны дополнительные параметры?
Изучите преимущества подписки, просмотрите учебные курсы, узнайте, как защитить свое устройство и т. д.
В сообществах можно задавать вопросы и отвечать на них, отправлять отзывы и консультироваться с экспертами разных профилей.
title | description | author | ms.author | ms.date | ms.service | ms.subservice | ms.topic | helpviewer_keywords |
---|---|---|---|---|---|---|---|---|
MSSQLSERVER_1205 |
MSSQLSERVER_1205 |
MashaMSFT |
mathoma |
04/04/2017 |
sql |
supportability |
reference |
1205 (Database Engine error) |
MSSQLSERVER_1205
[!INCLUDE SQL Server Azure SQL Database Azure SQL Managed Instance]
Details
Attribute | Value |
---|---|
Product Name | SQL Server |
Event ID | 1205 |
Event Source | MSSQLSERVER |
Component | SQLEngine |
Symbolic Name | LK_VICTIM |
Message Text | Transaction (Process ID %d) was deadlocked on %.*ls resources with another process and has been chosen as the deadlock victim. Rerun the transaction. |
Explanation
Resources are accessed in conflicting order on separate transactions, causing a deadlock. For example:
- Transaction1 updates Table1.Row1, while Transaction2 updates Table2.Row2
- Transaction1 tries to update Table2.Row2 but is blocked because Transaction2 hasn’t yet committed and hasn’t released its locks
- Transaction2 now tries to update Table1.Row1 but is blocked because Transaction1 hasn’t committed and hasn’t released its locks
- A deadlock occurs because Transaction1 is waiting for Transaction2 to complete, but Transaction2 is waiting for Transaction1 to complete.
The system will detect this deadlock and will choose one of the transactions involved as a ‘victim’. It will then issue this error message, rolling back the victim’s transaction. For detailed information, see Deadlocks.
User Action
Deadlocks are in most cases application-related issues and require application developers to make code changes. One approach when you receive error 1205 is to execute the queries again. See this blog for an example of how to retry — handle the deadlock and re-execute the query: Deadlock Simulator app for Developers: How to Handle a SQL Deadlock issue in Your App
You can also revise the application to avoid deadlocks. The transaction that was chosen as a victim can be retried and will likely succeed, depending on what operations are being executed simultaneously.
To prevent or avoid deadlocks from occurring, consider having all transactions access rows in the same order (Table1, then Table2). This way, although blocking might occur, a deadlock will be avoided.
For more information, see Handling Deadlocks and Minimizing Deadlocks.
I’m running the following MySQL UPDATE
statement:
mysql> update customer set account_import_id = 1;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
I’m not using a transaction, so why would I be getting this error? I even tried restarting my MySQL server and it didn’t help.
The table has 406,733 rows.
asked Apr 29, 2011 at 19:40
Jason SwettJason Swett
43k65 gold badges216 silver badges348 bronze badges
HOW TO FORCE UNLOCK for locked tables in MySQL:
Breaking locks like this may cause atomicity in the database to not be enforced on the sql statements that caused the lock.
This is hackish, and the proper solution is to fix your application that caused the locks. However, when dollars are on the line, a swift kick will get things moving again.
1) Enter MySQL
mysql -u your_user -p
2) Let’s see the list of locked tables
mysql> show open tables where in_use>0;
3) Let’s see the list of the current processes, one of them is locking your table(s)
mysql> show processlist;
4) Kill one of these processes
mysql> kill <put_process_id_here>;
Lars Andren
8,5337 gold badges41 silver badges56 bronze badges
answered Aug 12, 2014 at 17:33
Eric LeschinskiEric Leschinski
145k95 gold badges412 silver badges332 bronze badges
11
You are using a transaction; autocommit does not disable transactions, it just makes them automatically commit at the end of the statement.
What could be happening is, some other thread is holding a record lock on some record (you’re updating every record in the table!) for too long, and your thread is being timed out. Or maybe running multiple (2+) UPDATE queries on the same row during a single transaction.
You can see more details of the event by issuing a
SHOW ENGINE INNODB STATUS
after the event (in SQL editor). Ideally do this on a quiet test-machine.
answered Apr 29, 2011 at 20:54
5
mysql> set innodb_lock_wait_timeout=100;
Query OK, 0 rows affected (0.02 sec)
mysql> show variables like 'innodb_lock_wait_timeout';
+--------------------------+-------+
| Variable_name | Value |
+--------------------------+-------+
| innodb_lock_wait_timeout | 100 |
+--------------------------+-------+
Now trigger the lock again. You have 100 seconds time to issue a SHOW ENGINE INNODB STATUSG
to the database and see which other transaction is locking yours.
adhg
10.3k12 gold badges57 silver badges94 bronze badges
answered Nov 15, 2012 at 20:39
veenveen
1,5471 gold badge9 silver badges2 bronze badges
3
Take a look to see if your database is fine tuned, especially the transaction isolation. It isn’t a good idea to increase the innodb_lock_wait_timeout
variable.
Check your database transaction isolation level in MySQL:
mysql> SELECT @@GLOBAL.tx_isolation, @@tx_isolation, @@session.tx_isolation;
+-----------------------+-----------------+------------------------+
| @@GLOBAL.tx_isolation | @@tx_isolation | @@session.tx_isolation |
+-----------------------+-----------------+------------------------+
| REPEATABLE-READ | REPEATABLE-READ | REPEATABLE-READ |
+-----------------------+-----------------+------------------------+
1 row in set (0.00 sec)
You could get improvements changing the isolation level. Use the Oracle-like READ COMMITTED
instead of REPEATABLE READ
. REPEATABLE READ
is the InnoDB default.
mysql> SET tx_isolation = 'READ-COMMITTED';
Query OK, 0 rows affected (0.00 sec)
mysql> SET GLOBAL tx_isolation = 'READ-COMMITTED';
Query OK, 0 rows affected (0.00 sec)
Also, try to use SELECT FOR UPDATE
only if necessary.
Kirby
15k9 gold badges88 silver badges103 bronze badges
answered Mar 6, 2013 at 16:25
5
Something is blocking the execution of the query. Most likely another query updating, inserting or deleting from one of the tables in your query. You have to find out what that is:
SHOW PROCESSLIST;
Once you locate the blocking process, find its id
and run :
KILL {id};
Re-run your initial query.
answered Mar 16, 2016 at 12:12
BassMHLBassMHL
8,4039 gold badges50 silver badges66 bronze badges
2
mysql->SHOW PROCESSLIST;
kill xxxx;
and then kill which one in sleep. In my case it is 2156.
Giacomo M
4,3907 gold badges28 silver badges56 bronze badges
answered Jul 26, 2020 at 18:25
Ahmad SharifAhmad Sharif
4,1215 gold badges37 silver badges48 bronze badges
100% with what MarkR said. autocommit makes each statement a one statement transaction.
SHOW ENGINE INNODB STATUS
should give you some clues as to the deadlock reason. Have a good look at your slow query log too to see what else is querying the table and try to remove anything that’s doing a full tablescan. Row level locking works well but not when you’re trying to lock all of the rows!
answered Apr 29, 2011 at 21:07
James CJames C
14k1 gold badge34 silver badges43 bronze badges
Try to update the below two parameters as they must be having default values.
innodb_lock_wait_timeout = 50
innodb_rollback_on_timeout = ON
For checking parameter value you can use the below SQL.
SHOW GLOBAL VARIABLES LIKE ‘innodb_rollback_on_timeout’;
answered Jan 18, 2021 at 5:35
Can you update any other record within this table, or is this table heavily used? What I am thinking is that while it is attempting to acquire a lock that it needs to update this record the timeout that was set has timed out. You may be able to increase the time which may help.
answered Apr 29, 2011 at 19:48
John KaneJohn Kane
4,3631 gold badge24 silver badges42 bronze badges
2
If you’ve just killed a big query, it will take time to rollback
. If you issue another query before the killed query is done rolling back, you might get a lock timeout error. That’s what happened to me. The solution was just to wait a bit.
Details:
I had issued a DELETE query to remove about 900,000 out of about 1 million rows.
I ran this by mistake (removes only 10% of the rows):
DELETE FROM table WHERE MOD(id,10) = 0
Instead of this (removes 90% of the rows):
DELETE FROM table WHERE MOD(id,10) != 0
I wanted to remove 90% of the rows, not 10%. So I killed the process in the MySQL command line, knowing that it would roll back all the rows it had deleted so far.
Then I ran the correct command immediately, and got a lock timeout exceeded
error soon after. I realized that the lock might actually be the rollback
of the killed query still happening in the background. So I waited a few seconds and re-ran the query.
answered Jan 24, 2019 at 3:03
Buttle ButkusButtle Butkus
9,17613 gold badges79 silver badges120 bronze badges
1
In our case the problem did not have much to do with the locks themselves.
The issue was that one of our application endpoints needed to open 2 connections in parallel to process a single request.
Example:
- Open 1st connection
- Start transaction 1
- Lock 1 row in table1
- Open 2nd connection
- Start transaction 2
- Lock 1 row in table2
- Commit transaction 2
- Release 2nd connection
- Commit transaction 1
- Release 1st connection
Our application had a connection pool limited to 10 connections.
Unfortunately, under load, as soon as all connections were used the application stopped working and we started having this problem.
We had several requests that needed to open a second connection to complete, but could not due to the connection pool limit. As a consequence, those requests were keeping a lock on the table1 row for a long time leading the following requests that needed to lock the same row to throw this error.
Solution:
- In the short term, we patched the problem by increasing the connection pool limit.
- In the long term, we removed all nested connections, to fully solve the issue.
Tips:
You can easily check if you have nested connections by trying to lower your connection pool limit to 1 and test your application.
answered May 3, 2022 at 15:40
The number of rows is not huge… Create an index on account_import_id if its not the primary key.
CREATE INDEX idx_customer_account_import_id ON customer (account_import_id);
answered Jul 29, 2015 at 18:16
gladiatorgladiator
7221 gold badge9 silver badges16 bronze badges
1
Make sure the database tables are using InnoDB storage engine and READ-COMMITTED transaction isolation level.
You can check it by SELECT @@GLOBAL.tx_isolation, @@tx_isolation; on mysql console.
If it is not set to be READ-COMMITTED then you must set it. Make sure before setting it that you have SUPER privileges in mysql.
You can take help from http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html.
By setting this I think your problem will be get solved.
You might also want to check you aren’t attempting to update this in two processes at once. Users ( @tala ) have encountered similar error messages in this context, maybe double-check that…
I came from Google and I just wanted to add the solution that worked for me. My problem was I was trying to delete records of a huge table that had a lot of FK in cascade so I got the same error as the OP.
I disabled the autocommit
and then it worked just adding COMMIT
at the end of the SQL sentence. As far as I understood this releases the buffer bit by bit instead of waiting at the end of the command.
To keep with the example of the OP, this should have worked:
mysql> set autocommit=0;
mysql> update customer set account_import_id = 1; commit;
Do not forget to reactivate the autocommit
again if you want to leave the MySQL config as before.
mysql> set autocommit=1;
answered Apr 9, 2019 at 22:26
KamaeKamae
5432 gold badges9 silver badges19 bronze badges
Late to the party (as usual) however my issue was the fact that I wrote some bad SQL (being a novice) and several processes had a lock on the record(s) <— not sure the appropriate verbiage. I ended up having to just: SHOW PROCESSLIST
and then kill the IDs using KILL <id>
answered Apr 17, 2016 at 16:22
SmittySmitty
1,72515 silver badges21 bronze badges
This kind of thing happened to me when I was using php
language construct exit; in middle of transaction. Then this
transaction «hangs» and you need to kill mysql process (described above with processlist;)
answered Dec 2, 2016 at 11:22
TomoMihaTomoMiha
1,1901 gold badge13 silver badges12 bronze badges
In my instance, I was running an abnormal query to fix data. If you lock the tables in your query, then you won’t have to deal with the Lock timeout:
LOCK TABLES `customer` WRITE;
update customer set account_import_id = 1;
UNLOCK TABLES;
This is probably not a good idea for normal use.
For more info see: MySQL 8.0 Reference Manual
answered Nov 5, 2018 at 23:27
Jeff LuyetJeff Luyet
4143 silver badges10 bronze badges
I ran into this having 2 Doctrine DBAL connections, one of those as non-transactional (for important logs), they are intended to run parallel not depending on each other.
CodeExecution(
TransactionConnectionQuery()
TransactionlessConnectionQuery()
)
My integration tests were wrapped into transactions for data rollback after very test.
beginTransaction()
CodeExecution(
TransactionConnectionQuery()
TransactionlessConnectionQuery() // CONFLICT
)
rollBack()
My solution was to disable the wrapping transaction in those tests and reset the db data in another way.
answered Mar 25, 2019 at 14:36
fabpicofabpico
2,5564 gold badges26 silver badges43 bronze badges
We ran into this issue yesterday and after slogging through just about every suggested solution here, and several others from other answers/forums we ended up resolving it once we realized the actual issue.
Due to some poor planning, our database was stored on a mounted volume that was also receiving our regular automated backups. That volume had reached max capacity.
Once we cleared up some space and restarted, this error was resolved.
Note that we did also manually kill several of the processes: kill <process_id>;
so that may still be necessary.
Overall, our takeaway was that it was incredibly frustrating that none of our logs or warnings directly mentioned a lack of disk space, but that did seem to be the root cause.
answered Aug 5, 2021 at 21:25
SalSal
3112 silver badges5 bronze badges
I had similar error when using python to access mysql database.
The python program was using a while and for loop.
Closing cursor and link at appropriate line solved problem
https://github.com/nishishailesh/sensa_host_com/blob/master/sensa_write.py
see line 230
It appears that asking repeated link without closing previous link produced this error
answered Nov 21, 2021 at 19:31
I’ve faced a similar issue when doing some testing.
Reason — In my case transaction was not committed from my spring boot application because I killed the @transactional function during the execution(when the function was updating some rows). Due to which transaction was never committed to the database(MySQL).
Result — not able to update those rows from anywhere. But able to update other rows of the table.
mysql> update some_table set some_value = "Hello World" where id = 1;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
Solution — killed all the MySQL processes using
- sudo killall -9 mysqld
- sudo killall -9 mysqld_safe (restarting the server when an error occurs and logging runtime information to an error log. Not required in my case)
answered May 11, 2022 at 19:42
My scenario was failing to commit after an update or delete.
show processlist
showed a long list of sleeping connections.
answered Mar 8 at 17:12
JayJay
1731 silver badge7 bronze badges
Had this same error, even though I was only updating one table with one entry, but after restarting mysql, it was resolved.
answered Sep 11, 2017 at 11:38
- Remove From My Forums
-
Question
-
Hi,
I am getting deadlock exception»SQL SERVER — Fix : Error 1205 : Transaction (Process ID) was deadlocked on resources with another process and has been chosen as the deadlock victim. Rerun the transaction».
I am using just a single application there is no problem but when two or more clients are inserting records concurrently, I get the deadlock excpetion.
Can anyone give me the solution of this problem.deadlock occurs in Stored procedure.I am using sql server 2005.
One of the most popular InnoDB’s errors is InnoDB lock wait timeout exceeded, for example:
SQLSTATE[HY000]: General error: 1205 Lock wait timeout exceeded; try restarting transaction
The above simply means the transaction has reached the innodb_lock_wait_timeout while waiting to obtain an exclusive lock which defaults to 50 seconds. The common causes are:
- The offensive transaction is not fast enough to commit or rollback the transaction within innodb_lock_wait_timeout duration.
- The offensive transaction is waiting for row lock to be released by another transaction.
The Effects of a InnoDB Lock Wait Timeout
InnoDB lock wait timeout can cause two major implications:
- The failed statement is not being rolled back by default.
- Even if innodb_rollback_on_timeout is enabled, when a statement fails in a transaction, ROLLBACK is still a more expensive operation than COMMIT.
Let’s play around with a simple example to better understand the effect. Consider the following two tables in database mydb:
mysql> CREATE SCHEMA mydb;
mysql> USE mydb;
The first table (table1):
mysql> CREATE TABLE table1 ( id INT PRIMARY KEY AUTO_INCREMENT, data VARCHAR(50));
mysql> INSERT INTO table1 SET data = 'data #1';
The second table (table2):
mysql> CREATE TABLE table2 LIKE table1;
mysql> INSERT INTO table2 SET data = 'data #2';
We executed our transactions in two different sessions in the following order:
Ordering |
Transaction #1 (T1) |
Transaction #2 (T2) |
1 |
SELECT * FROM table1; (OK) |
SELECT * FROM table1; (OK) |
2 |
UPDATE table1 SET data = ‘T1 is updating the row’ WHERE id = 1; (OK) |
|
3 |
UPDATE table2 SET data = ‘T2 is updating the row’ WHERE id = 1; (OK) |
|
4 |
UPDATE table1 SET data = ‘T2 is updating the row’ WHERE id = 1; (Hangs for a while and eventually returns an error “Lock wait timeout exceeded; try restarting transaction”) |
|
5 |
COMMIT; (OK) |
|
6 |
COMMIT; (OK) |
However, the end result after step #6 might be surprising if we did not retry the timed out statement at step #4:
mysql> SELECT * FROM table1 WHERE id = 1;
+----+-----------------------------------+
| id | data |
+----+-----------------------------------+
| 1 | T1 is updating the row |
+----+-----------------------------------+
mysql> SELECT * FROM table2 WHERE id = 1;
+----+-----------------------------------+
| id | data |
+----+-----------------------------------+
| 1 | T2 is updating the row |
+----+-----------------------------------+
After T2 was successfully committed, one would expect to get the same output “T2 is updating the row” for both table1 and table2 but the results show that only table2 was updated. One might think that if any error encounters within a transaction, all statements in the transaction would automatically get rolled back, or if a transaction is successfully committed, the whole statements were executed atomically. This is true for deadlock, but not for InnoDB lock wait timeout.
Unless you set innodb_rollback_on_timeout=1 (default is 0 – disabled), automatic rollback is not going to happen for InnoDB lock wait timeout error. This means, by following the default setting, MySQL is not going to fail and rollback the whole transaction, nor retrying again the timed out statement and just process the next statements until it reaches COMMIT or ROLLBACK. This explains why transaction T2 was partially committed!
The InnoDB documentation clearly says “InnoDB rolls back only the last statement on a transaction timeout by default”. In this case, we do not get the transaction atomicity offered by InnoDB. The atomicity in ACID compliant is either we get all or nothing of the transaction, which means partial transaction is merely unacceptable.
Dealing With a InnoDB Lock Wait Timeout
So, if you are expecting a transaction to auto-rollback when encounters an InnoDB lock wait error, similarly as what would happen in deadlock, set the following option in MySQL configuration file:
innodb_rollback_on_timeout=1
A MySQL restart is required. When deploying a MySQL-based cluster, ClusterControl will always set innodb_rollback_on_timeout=1 on every node. Without this option, your application has to retry the failed statement, or perform ROLLBACK explicitly to maintain the transaction atomicity.
To verify if the configuration is loaded correctly:
mysql> SHOW GLOBAL VARIABLES LIKE 'innodb_rollback_on_timeout';
+----------------------------+-------+
| Variable_name | Value |
+----------------------------+-------+
| innodb_rollback_on_timeout | ON |
+----------------------------+-------+
To check whether the new configuration works, we can track the com_rollback counter when this error happens:
mysql> SHOW GLOBAL STATUS LIKE 'com_rollback';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| Com_rollback | 1 |
+---------------+-------+
Tracking the Blocking Transaction
There are several places that we can look to track the blocking transaction or statements. Let’s start by looking into InnoDB engine status under TRANSACTIONS section:
mysql> SHOW ENGINE INNODB STATUSG
------------
TRANSACTIONS
------------
...
---TRANSACTION 3100, ACTIVE 2 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)
MySQL thread id 50, OS thread handle 139887555282688, query id 360 localhost ::1 root updating
update table1 set data = 'T2 is updating the row' where id = 1
------- TRX HAS BEEN WAITING 2 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 6 page no 4 n bits 72 index PRIMARY of table `mydb`.`table1` trx id 3100 lock_mode X locks rec but not gap waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
0: len 4; hex 80000001; asc ;;
1: len 6; hex 000000000c19; asc ;;
2: len 7; hex 020000011b0151; asc Q;;
3: len 22; hex 5431206973207570646174696e672074686520726f77; asc T1 is updating the row;;
------------------
---TRANSACTION 3097, ACTIVE 46 sec
2 lock struct(s), heap size 1136, 1 row lock(s), undo log entries 1
MySQL thread id 48, OS thread handle 139887556167424, query id 358 localhost ::1 root
Trx read view will not see trx with id >= 3097, sees < 3097
From the above information, we can get an overview of the transactions that are currently active in the server. Transaction 3097 is currently locking a row that needs to be accessed by transaction 3100. However, the above output does not tell us the actual query text that could help us figuring out which part of the query/statement/transaction that we need to investigate further. By using the blocker MySQL thread ID 48, let’s see what we can gather from MySQL processlist:
mysql> SHOW FULL PROCESSLIST;
+----+-----------------+-----------------+--------------------+---------+------+------------------------+-----------------------+
| Id | User | Host | db | Command | Time | State | Info |
+----+-----------------+-----------------+--------------------+---------+------+------------------------+-----------------------+
| 4 | event_scheduler | localhost | | Daemon | 5146 | Waiting on empty queue | |
| 10 | root | localhost:56042 | performance_schema | Query | 0 | starting | show full processlist |
| 48 | root | localhost:56118 | mydb | Sleep | 145 | | |
| 50 | root | localhost:56122 | mydb | Sleep | 113 | | |
+----+-----------------+-----------------+--------------------+---------+------+------------------------+-----------------------+
Thread ID 48 shows the command as ‘Sleep’. Still, this does not help us much to know which statements that block the other transaction. This is because the statement in this transaction has been executed and this open transaction is basically doing nothing at the moment. We need to dive further down to see what is going on with this thread.
For MySQL 8.0, the InnoDB lock wait instrumentation is available under data_lock_waits table inside performance_schema database (or innodb_lock_waits table inside sys database). If a lock wait event is happening, we should see something like this:
mysql> SELECT * FROM performance_schema.data_lock_waitsG
***************************[ 1. row ]***************************
ENGINE | INNODB
REQUESTING_ENGINE_LOCK_ID | 139887595270456:6:4:2:139887487554680
REQUESTING_ENGINE_TRANSACTION_ID | 3100
REQUESTING_THREAD_ID | 89
REQUESTING_EVENT_ID | 8
REQUESTING_OBJECT_INSTANCE_BEGIN | 139887487554680
BLOCKING_ENGINE_LOCK_ID | 139887595269584:6:4:2:139887487548648
BLOCKING_ENGINE_TRANSACTION_ID | 3097
BLOCKING_THREAD_ID | 87
BLOCKING_EVENT_ID | 9
BLOCKING_OBJECT_INSTANCE_BEGIN | 139887487548648
Note that in MySQL 5.6 and 5.7, the similar information is stored inside innodb_lock_waits table under information_schema database. Pay attention to the BLOCKING_THREAD_ID value. We can use the this information to look for all statements being executed by this thread in events_statements_history table:
mysql> SELECT * FROM performance_schema.events_statements_history WHERE `THREAD_ID` = 87;
0 rows in set
It looks like the thread information is no longer there. We can verify by checking the minimum and maximum value of the thread_id column in events_statements_history table with the following query:
mysql> SELECT min(`THREAD_ID`), max(`THREAD_ID`) FROM performance_schema.events_statements_history;
+------------------+------------------+
| min(`THREAD_ID`) | max(`THREAD_ID`) |
+------------------+------------------+
| 98 | 129 |
+------------------+------------------+
The thread that we were looking for (87) has been truncated from the table. We can confirm this by looking at the size of event_statements_history table:
mysql> SELECT @@performance_schema_events_statements_history_size;
+-----------------------------------------------------+
| @@performance_schema_events_statements_history_size |
+-----------------------------------------------------+
| 10 |
+-----------------------------------------------------+
The above means the events_statements_history can only store the last 10 threads. Fortunately, performance_schema has another table to store more rows called events_statements_history_long, which stores similar information but for all threads and it can contain way more rows:
mysql> SELECT @@performance_schema_events_statements_history_long_size;
+----------------------------------------------------------+
| @@performance_schema_events_statements_history_long_size |
+----------------------------------------------------------+
| 10000 |
+----------------------------------------------------------+
However, you will get an empty result if you try to query the events_statements_history_long table for the first time. This is expected because by default, this instrumentation is disabled in MySQL as we can see in the following setup_consumers table:
mysql> SELECT * FROM performance_schema.setup_consumers;
+----------------------------------+---------+
| NAME | ENABLED |
+----------------------------------+---------+
| events_stages_current | NO |
| events_stages_history | NO |
| events_stages_history_long | NO |
| events_statements_current | YES |
| events_statements_history | YES |
| events_statements_history_long | NO |
| events_transactions_current | YES |
| events_transactions_history | YES |
| events_transactions_history_long | NO |
| events_waits_current | NO |
| events_waits_history | NO |
| events_waits_history_long | NO |
| global_instrumentation | YES |
| thread_instrumentation | YES |
| statements_digest | YES |
+----------------------------------+---------+
To activate table events_statements_history_long, we need to update the setup_consumers table as below:
mysql> UPDATE performance_schema.setup_consumers SET enabled = 'YES' WHERE name = 'events_statements_history_long';
Verify if there are rows in the events_statements_history_long table now:
mysql> SELECT count(`THREAD_ID`) FROM performance_schema.events_statements_history_long;
+--------------------+
| count(`THREAD_ID`) |
+--------------------+
| 4 |
+--------------------+
Cool. Now we can wait until the InnoDB lock wait event raises again and when it is happening, you should see the following row in the data_lock_waits table:
mysql> SELECT * FROM performance_schema.data_lock_waitsG
***************************[ 1. row ]***************************
ENGINE | INNODB
REQUESTING_ENGINE_LOCK_ID | 139887595270456:6:4:2:139887487555024
REQUESTING_ENGINE_TRANSACTION_ID | 3083
REQUESTING_THREAD_ID | 60
REQUESTING_EVENT_ID | 9
REQUESTING_OBJECT_INSTANCE_BEGIN | 139887487555024
BLOCKING_ENGINE_LOCK_ID | 139887595269584:6:4:2:139887487548648
BLOCKING_ENGINE_TRANSACTION_ID | 3081
BLOCKING_THREAD_ID | 57
BLOCKING_EVENT_ID | 8
BLOCKING_OBJECT_INSTANCE_BEGIN | 139887487548648
Again, we use the BLOCKING_THREAD_ID value to filter all statements that have been executed by this thread against events_statements_history_long table:
mysql> SELECT `THREAD_ID`,`EVENT_ID`,`EVENT_NAME`, `CURRENT_SCHEMA`,`SQL_TEXT` FROM events_statements_history_long
WHERE `THREAD_ID` = 57
ORDER BY `EVENT_ID`;
+-----------+----------+-----------------------+----------------+----------------------------------------------------------------+
| THREAD_ID | EVENT_ID | EVENT_NAME | CURRENT_SCHEMA | SQL_TEXT |
+-----------+----------+-----------------------+----------------+----------------------------------------------------------------+
| 57 | 1 | statement/sql/select | | select connection_id() |
| 57 | 2 | statement/sql/select | | SELECT @@VERSION |
| 57 | 3 | statement/sql/select | | SELECT @@VERSION_COMMENT |
| 57 | 4 | statement/com/Init DB | | |
| 57 | 5 | statement/sql/begin | mydb | begin |
| 57 | 7 | statement/sql/select | mydb | select 'T1 is in the house' |
| 57 | 8 | statement/sql/select | mydb | select * from table1 |
| 57 | 9 | statement/sql/select | mydb | select 'some more select' |
| 57 | 10 | statement/sql/update | mydb | update table1 set data = 'T1 is updating the row' where id = 1 |
+-----------+----------+-----------------------+----------------+----------------------------------------------------------------+
Finally, we found the culprit. We can tell by looking at the sequence of events of thread 57 where the above transaction (T1) still has not finished yet (no COMMIT or ROLLBACK), and we can see the very last statement has obtained an exclusive lock to the row for update operation which needed by the other transaction (T2) and just hanging there. That explains why we see ‘Sleep’ in the MySQL processlist output.
As we can see, the above SELECT statement requires you to get the thread_id value beforehand. To simplify this query, we can use IN clause and a subquery to join both tables. The following query produces an identical result like the above:
mysql> SELECT `THREAD_ID`,`EVENT_ID`,`EVENT_NAME`, `CURRENT_SCHEMA`,`SQL_TEXT` from events_statements_history_long WHERE `THREAD_ID` IN (SELECT `BLOCKING_THREAD_ID` FROM data_lock_waits) ORDER BY `EVENT_ID`;
+-----------+----------+-----------------------+----------------+----------------------------------------------------------------+
| THREAD_ID | EVENT_ID | EVENT_NAME | CURRENT_SCHEMA | SQL_TEXT |
+-----------+----------+-----------------------+----------------+----------------------------------------------------------------+
| 57 | 1 | statement/sql/select | | select connection_id() |
| 57 | 2 | statement/sql/select | | SELECT @@VERSION |
| 57 | 3 | statement/sql/select | | SELECT @@VERSION_COMMENT |
| 57 | 4 | statement/com/Init DB | | |
| 57 | 5 | statement/sql/begin | mydb | begin |
| 57 | 7 | statement/sql/select | mydb | select 'T1 is in the house' |
| 57 | 8 | statement/sql/select | mydb | select * from table1 |
| 57 | 9 | statement/sql/select | mydb | select 'some more select' |
| 57 | 10 | statement/sql/update | mydb | update table1 set data = 'T1 is updating the row' where id = 1 |
+-----------+----------+-----------------------+----------------+----------------------------------------------------------------+
However, it is not practical for us to execute the above query whenever InnoDB lock wait event occurs. Apart from the error from the application, how would you know that the lock wait event is happening? We can automate this query execution with the following simple Bash script, called track_lockwait.sh:
$ cat track_lockwait.sh
#!/bin/bash
## track_lockwait.sh
## Print out the blocking statements that causing InnoDB lock wait
INTERVAL=5
DIR=/root/lockwait/
[ -d $dir ] || mkdir -p $dir
while true; do
check_query=$(mysql -A -Bse 'SELECT THREAD_ID,EVENT_ID,EVENT_NAME,CURRENT_SCHEMA,SQL_TEXT FROM events_statements_history_long WHERE THREAD_ID IN (SELECT BLOCKING_THREAD_ID FROM data_lock_waits) ORDER BY EVENT_ID')
# if $check_query is not empty
if [[ ! -z $check_query ]]; then
timestamp=$(date +%s)
echo $check_query > $DIR/innodb_lockwait_report_${timestamp}
fi
sleep $INTERVAL
done
Apply executable permission and daemonize the script in the background:
$ chmod 755 track_lockwait.sh
$ nohup ./track_lockwait.sh &
Now, we just need to wait for the reports to be generated under the /root/lockwait directory. Depending on the database workload and row access patterns, you might probably see a lot of files under this directory. Monitor the directory closely otherwise it would be flooded with too many report files.
If you are using ClusterControl, you can enable the Transaction Log feature under Performance -> Transaction Log where ClusterControl will provide a report on deadlocks and long-running transactions which will ease up your life in finding the culprit.
Conclusion
In summary, if we face a “Lock Wait Timeout Exceeded” error in MySQL, we need to first understand the effects that such an error can have to our infrastructure, then track the offensive transaction and act on it either with shell scripts like track_lockwait.sh, or database management software like ClusterControl.
If you decide to go with shell scripts, just bear in mind that they may save you money but will cost you time, as you’d need to know a thing or two about how they work, apply permissions, and possibly make them run in the background, and if you do get lost in the shell jungle, we can help.
Whatever you decide to implement, make sure to follow us on Twitter or subscribe to our RSS feed to get more tips on improving the performance of both your software and the databases backing it, such as this post covering 6 common failure scenarios in MySQL.