Mysql 異常:Lock wait timeout exceeded; try restarting transaction的解決辦法
技術標籤:資料庫
問題現象
介面響應時間超長,耗時幾十秒才返回錯誤提示,後臺日誌中出現Lock wait timeout exceeded; try restarting transaction
的錯誤
<-- java.sql.SQLException: Lock wait timeout exceeded; try restarting transaction at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1074) at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4120) at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4052) at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2503) at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2664) at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2794) at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:2155) at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2458) at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2375) at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2359) at com.trs.components.wcm.publish.element.PublishContentDocumentImpl.setPublishTimeAndURL(PublishContentDocumentImpl.java:851) at com.trs.components.common.publish.domain.publisher.PageGenerator.updateContentPublishTime(PageGenerator.java:236) at com.trs.components.common.publish.domain.publisher.PageGenerator.generateDetail(PageGenerator.java:216) at com.trs.components.common.publish.domain.taskdispatch.PageTaskWorker.executeTask(PageTaskWorker.java:278) at com.trs.components.common.publish.domain.taskdispatch.PageTaskWorker.run(PageTaskWorker.java:153) at com.trs.components.common.publish.domain.taskdispatch.ThreadPool$Worker.run(ThreadPool.java:56)
問題場景
1、在同一事務內先後對同一條資料進行插入和更新操作;
2、分散式服務操作同一條記錄;
3、瞬時出現高並發現象;
問題原因
1、在高併發的情況下,Spring事物造成資料庫死鎖,後續操作超時丟擲異常。
2、Mysql資料庫採用InnoDB模式,預設引數:innodb_lock_wait_timeout設定鎖等待的時間是50s,一旦資料庫鎖超過這個時間就會報錯
解決方法
方法一:調整超時引數
mysql官方文件如下:
當鎖等待超時後innodb引擎報此錯誤,等待時間過長的語句被回滾(不是整個事務)。如果想讓SQL語句等待其他事務更長時間之後完成,你可以增加引數innodb_lock_wait_timeout配置的值。如果有太多長時間執行的有鎖的事務,你可以減小這個innodb_lock_wait_timeout的值,在特別繁忙的系統,你可以減小併發。
InnoDB事務等待一個行級鎖的時間最長時間(單位是秒),超過這個時間就會放棄。預設值是50秒。一個事務A試圖訪問一行資料,但是這行資料正在被另一個innodb事務B鎖定,此時事務A就會等待事務B釋放鎖,等待超過innodb_lock_wait_timeout設定的值就會報錯ERROR 1205 (HY000):
innodb_lock_wait_timeout是動態引數,預設值50秒,最小值是1秒,最大值是1073741824;
set innodb_lock_wait_timeout=1500等價於set session隻影響當前sessio。set global innodb_lock_wait_timeout=1500作為全域性的修改方式,只會影響修改之後開啟的session,不能改變當前session。
mysql> set GLOBAL innodb_lock_wait_timeout=1500;
方法二:解決死鎖
1、檢視資料庫當前的程序
show processlist會顯示出當前正在執行的sql語句列表,找到消耗資源最大的那條語句對應的id.
mysql> show processlist;
+---------+------+-------------------+--------------------+---------+-------+-------+------------------+
| Id | User | Host | db | Command | Time | State | Info |
+---------+------+-------------------+--------------------+---------+-------+-------+------------------+
| 3205081 | root | 172.19.2.8:50317 | ******** | Sleep | 16485 | | NULL |
| 3210354 | root | 172.19.2.8:51066 | information_schema | Sleep | 3569 | | NULL |
| 3210630 | root | 172.19.2.12:61845 | ******** | Query | 0 | init | show processlist |
+---------+------+-------------------+--------------------+---------+-------+-------+------------------+
10 rows in set (0.00 sec)
2、檢視當前的鎖和事務
在5.5中,information_schema 庫中增加了三個關於鎖的表(inndodb引擎):
- innodb_trx ## 當前執行的所有事務
- innodb_locks ## 當前出現的鎖,檢視正在鎖的事務
- innodb_lock_waits ## 鎖等待的對應關係 ,檢視等待鎖的事務
當前執行的所有事務
mysql> SELECT * FROM information_schema.INNODB_TRX;
當前出現的鎖
mysql> SELECT * FROM information_schema.INNODB_LOCKs;
鎖等待的對應關係
mysql> SELECT * FROM information_schema.INNODB_LOCK_waits;
看裡面是否有正在鎖定的事務執行緒,看看ID是否在show processlist裡面的sleep執行緒中,如果是,就證明這個sleep的執行緒事務一直沒有commit或者rollback而是卡住了
3、查詢產生鎖的具體sql
根據具體的sql,就能看出是不是死鎖了,並且可以確定具體是執行了什麼業務,是否可以kill;
select
a.trx_id 事務id ,
a.trx_mysql_thread_id 事務執行緒id,
a.trx_query 事務sql
from
INFORMATION_SCHEMA.INNODB_LOCKS b,
INFORMATION_SCHEMA.innodb_trx a
where
b.lock_trx_id=a.trx_id;
4、殺掉死鎖的事務
查詢出所有有鎖的事務對應的執行緒ID(注意是執行緒id,不是事務id),通過information_schema.processlist表中的連線資訊生成需要處理掉的MySQL連線的語句臨時檔案,然後執行臨時檔案中生成的指令。
mysql> select concat('KILL ',a.trx_mysql_thread_id ,';') from INFORMATION_SCHEMA.INNODB_LOCKS b,INFORMATION_SCHEMA.innodb_trx a where b.lock_trx_id=a.trx_id;
+------------------------+
| concat('KILL ',id,';') |
+------------------------+
| KILL 3205081; |
| KILL 3210354; |
| KILL 3210630; |
+------------------------+
18 rows in set (0.00 sec)
如果太多的話可以匯出到txt再批量執行
mysql> select concat('KILL ',a.trx_mysql_thread_id ,';') from INFORMATION_SCHEMA.INNODB_LOCKS b,INFORMATION_SCHEMA.innodb_trx a where b.lock_trx_id=a.trx_id into outfile '/tmp/kill.txt';
KILL命令允許自選的CONNECTION或QUERY修改符:KILL CONNECTION與不含修改符的KILL一樣:它會終止與給定的thread_id有關的連線。KILL QUERY會終止連線當前正在執行的語句,但是會保持連線的原狀。KILL命令的語法格式如下:
KILL [CONNECTION | QUERY] thread_id
執行kill命令
mysql> kill 3205081;
Query OK, 0 rows affected (0.00 sec)
mysql> kill 3210354;
Query OK, 0 rows affected (0.00 sec)
參考:
Lock wait timeout exceeded:http://blog.itpub.net/29654823/viewspace-2150471/
MySQL事務鎖問題:https://cloud.tencent.com/developer/article/1356959