问题
场景复原调试
在java代码中debug操作,进入到事务中,操作user表中关于userId=197的一行中任意一个字段然后循环或者时间Thread.sleep等待,此时这行就被锁住了。如果当前期间对这行的某个字段进行更新,则这个操作将会由于在事务中userId=197这行已经被锁定而进入阻塞等待状态,如果等待的时间超过数据库中设置的某个值则会报错。如果在阻塞是将内处于事务中的这行,已经将锁释放。
com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:123) ~[mysql-connector-java-8.0.15.jar:8.0.15]
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:97) ~[mysql-connector-java-8.0.15.jar:8.0.15]
at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122) ~[mysql-connector-java-8.0.15.jar:8.0.15]
at com.mysql.cj.jdbc.ClientPreparedSta
初步诊断方向
为什么事务开启后没有ROLLBACK或者COMMIT。
解决探索方向: [MySQL] 生产环境MySQL数据库事务一直在RUNNING
问题场景复现:
当用户执行进行下单的时候用户在user表的数据被锁住了,不能修改字段内容,但是可以查询。只有用户的这行被锁住,其他用户仍然可更新。
基础知识
innodb_trx 事务表的每个字段
trx_id:事务ID。
trx_state:事务状态,有以下几种状态:RUNNING、LOCK WAIT、ROLLING BACK 和 COMMITTING。
trx_started:事务开始时间。
trx_requested_lock_id:事务当前正在等待锁的标识,可以和 INNODB_LOCKS 表 JOIN 以得到更多详细信息。
trx_wait_started:事务开始等待的时间。
trx_weight:事务的权重。
trx_mysql_thread_id:事务线程 ID,可以和 PROCESSLIST 表 JOIN。
trx_query:事务正在执行的 SQL 语句。
trx_operation_state:事务当前操作状态。
trx_tables_in_use:当前事务执行的 SQL 中使用的表的个数。
trx_tables_locked:当前执行 SQL 的行锁数量。
trx_lock_structs:事务保留的锁数量。
trx_lock_memory_bytes:事务锁住的内存大小,单位为 BYTES。
trx_rows_locked:事务锁住的记录数。包含标记为 DELETED,并且已经保存到磁盘但对事务不可见的行。
trx_rows_modified:事务更改的行数。
trx_concurrency_tickets:事务并发票数。
trx_isolation_level:当前事务的隔离级别。
trx_unique_checks:是否打开唯一性检查的标识。
trx_foreign_key_checks:是否打开外键检查的标识。
trx_last_foreign_key_error:最后一次的外键错误信息。
trx_adaptive_hash_latched:自适应散列索引是否被当前事务锁住的标识。
trx_adaptive_hash_timeout:是否立刻放弃为自适应散列索引搜索 LATCH 的标识。
lock_id:锁 ID。
lock_trx_id:拥有锁的事务 ID。可以和 INNODB_TRX 表 JOIN 得到事务的详细信息。
lock_mode:锁的模式。有如下锁类型:行级锁包括:S、X、IS、IX,分别代表:共享锁、排它锁、意向共享锁、意向排它锁。表级锁包括:S_GAP、X_GAP、IS_GAP、IX_GAP 和 AUTO_INC,分别代表共享间隙锁、排它间隙锁、意向共享间隙锁、意向排它间隙锁和自动递增锁。
lock_type:锁的类型。RECORD 代表行级锁,TABLE 代表表级锁。
lock_table:被锁定的或者包含锁定记录的表的名称。
lock_index:当 LOCK_TYPE=’RECORD’ 时,表示索引的名称;否则为 NULL。
lock_space:当 LOCK_TYPE=’RECORD’ 时,表示锁定行的表空间 ID;否则为 NULL。
requesting_trx_id:请求事务的 ID。
requested_lock_id:事务所等待的锁定的 ID。可以和 INNODB_LOCKS 表 JOIN。
blocking_trx_id:阻塞事务的 ID。
blocking_lock_id:某一事务的锁的 ID,该事务阻塞了另一事务的运行。可以和 INNODB_LOCKS 表 JOIN。
SHOW ENGINE INNODB STATUS
—TRANSACTION 21924476, ACTIVE 3958 sec
应急解决:Kill掉information_schema.innodb_trx表的trx_mysql_thread_id
kill 200639
一个参数救活被hang住的数据库!
设置这个参数后只针对新的连接有效,正在执行的连接无效,固我们让运维又依次重启前端应用后,数据库终于复活成功!
innodb_kill_idle_transaction
show global variables like 'innodb_kill_idle_transaction'
set global innodb_kill_idle_transaction=30
LOCK WAIT过一段时间会消失,但是RUNNING不会消失
常用命令
-- 事务运行
SELECT * FROM information_schema.innodb_trx;
-- 等待锁
SELECT * FROM information_schema.innodb_lock_waits;
SELECT * FROM information_schema.innodb_locks;
-- 事务相关的线程
show processlist;
-- 根据事务线程id找到具体信息
select * from information_schema.PROCESSLIST where id=200639
show status like '%lock%';
SHOW ENGINE INNODB STATUS
show global variables like 'innodb_kill_idle_transaction'
set global innodb_kill_idle_transaction=30
-- 手动去掉事务
kill 200639
问题原因发现
今天开早会的时候,将遇到的问题描述了一遍,leader根据他的经验分享了一个观点,表的那行被锁住,有很大原因是因为session(用户的会话没有完全结束)导致数据仍然处于加锁状态。
根据:1)事务还是一直处于挂起状态running始终没有结束的观测结果和 2)早会上得到的tips,我需要找到那个第一次加锁的事务,观察在那个事务里面到底发生了什么,为什么没有正确的rollback(回滚)或commit(提交)。根据事故发生的时间点我找到了当时执行的代码状态。
此时该事务卡在赠品库存的业务处理中
,导致该事务一直处于running,并没有发生错误而进行rollback(回滚)操作,同时也没有进行成功时的commit.就这样一直处于running中。(此时mysql的innodb_kill_idle_transaction是为0
的,并没有使用DBA的后台超过一定时间的域值后去主动kill掉这个事务线程Id),这就导致这个事务一直存在。
由于在这个事务中已经update了user表,当同样一个用户再次执行同样的操作时,由于user表中关于这行已经被上一个事务锁定,此时的线程事务需要等待上一个事务释放该行锁,才能执行更新update user的操作。但是在该事务代码中存在一个会出现死循环的代码片段,导致上一个事务一直不结束,即一直持有这个行锁,导致这个用户第二次请求时,同样执行执行这段代码,当需要获取user表的行锁时,导致一直等待,由于等待时间太久,所以报错。
在事务中存在的一段代码片段:导致事务一直处于running状态。