1. 程式人生 > 其它 >Mysql 異常:Lock wait timeout exceeded; try restarting transaction的解決辦法

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