记一次 MySQL出现“Lock wait timeout”错误的原因

Posted 蝉动

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了记一次 MySQL出现“Lock wait timeout”错误的原因相关的知识,希望对你有一定的参考价值。

先说原因: 手动开启事务,由于处理业务时间过长,既不提交也未报错回滚,长时间占用事务就会出现这种情况,错误
关键字:trx_state为 running

故障场景:在测试环境中,在修改订单中偶现Lock wait timeout,且一直重复出现
初步定位: 采用下列命令排查
select * from INFORMATION_SCHEMA.innodb_locks;
SELECT * FROM sys.innodb_lock_waits;
SELECT * FROM INFORMATION_SCHEMA.innodb_trx;
SELECT * FROM INFORMATION_SCHEMA.processlist;

innodb_locks中发现互斥锁,且等待语句仅仅为
update order set status = ‘1’ where id = ‘1593507966307274753’;

定位到锁住的事务 trx_id 340312222,发现事务running时间过长,且trx_query为null,这个是事务既没有提交也没有回滚的含义,那么就知道是事务340312222 一直占用order表,导致其他的DML操作无法继续进行下去,且超时等待
ps:没有选择直接延长lock wait time的时间,是因为没有数据库super权限
SELECT * FROM INFORMATION_SCHEMA.innodb_trx;


发现是订单表被锁了,且应该不是行锁,行锁只是锁住当前行,单条update主键语句,如果被锁,我的思路是应该是整表被锁了或者页锁,经过排查是表锁
进一步发现被等待的事务,是当天晚上19:40开始的,且一直未commit和rollback,长时间开启事务,除非连接断开或者被回收,才会关闭事务,排查事务原因,由于没有其他信息,故在系统业务日志中查看19:40左右的提交日志,定位到是手动开启的事务,

    transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);

	//todo 更新了订单数据
    //手动提交事务
    dataSourceTransactionManager.commit(transactionStatus);
 catch (Exception e) 
    dataSourceTransactionManager.rollback(transactionStatus);
    e.printStackTrace();
    return Result.error("推送材积信息失败,请联系管理员!");

且在逻辑中有查询和修改订单的逻辑,且有大批量处理数据,时间过长,导致既不提交也不会滚的情况,长时间的占用事务

参考文章:
MySQL出现“Lock wait timeout exceeded”错误的原因是什么?
mysql 事务一直running_事务一直running?记录一次事务异常导致的下单阻塞

记一次mysql的数据恢复

数据库版本:MySql5.7

操作系统:CentOS 7.7

引:我之前一直使用 Sql Server ,最近自己创业,为了省点钱,用了MySql

某天,我悠然的写着代码,调着BUG。我们的运营大佬突然跟我反馈了一个线上版本的问题:

技术图片

说是数据突然不见了,我没怎么在意,心里在想,八成是客户自己把数据删了?? (PS:开发那么多年大部分灵异的反馈都是客户操作失误)

然而过了两分钟,运营大佬又反馈另一个客户也出现同样问题

技术图片

 

 

 此时我感觉不妙,但一时也想不起是什么问题,几分钟后,我突然.....想起了三天前把数据从 Sql Server 数据迁移到 MySql时写的那个测试脚本

技术图片

我把它挂在了MVC Home/Index 路由下了。后来我发现navicat可以很方便的导入数据,就把它给忘了...忘了....忘了...,然后更新到了线上环境,今天突然不知道哪个没事干的兄弟直接访问了一下我的项目域名。默认跳转Home/Index ,结果悲剧了,数据恢复到了三天前...,万幸的是我这个脚本只写了一个表,也就是说,只有一张表出了点问题。

当务之急是赶紧恢复数据。Sql Server数据恢复我有经验,一句脚本就可以恢复到某个时间点,特方便。但MySql没恢复过数据【抓虾】

在恢复之前,我先做了一个操作,把需要恢复的表主键自增起始值拔高,避免恢复期间新增加的数据跟老数据主键冲突。

然后上网一搜,找到了一个类似于sqlserver的解决方案:binlog 日志恢复。以下是相关命令

1、链接数据库,查询日志开启状态(ON开启)(如果你的数据库没有开启这个,估计用不了下面的方法了,万幸我开启了)

mysql -u root -p
MySql>show variables like ‘log_%‘;

(看到有开启日志之后,我心已经放下半个,心里想这不就跟sqlserver一样,选个前几分钟的时间点,恢复那张表不就行了)

2、查询日志操作(看看已经记录了哪些日志)

MySql>show logs;

3、查看master状态(看现在哪个日志文件在用)

MySql>show master status;

4、刷新日志(让数据库日志从一个新的文件开始记录,老的我要用来做恢复)

flush logs;

5、导出整个库某个时间点之前的操作到sql(不要直接跟着操作哦,后面有坑)

mysqlbinlog /www/server/data/mysql-bin.000004(这个是日志文件的地址) --skip-gtids=true --stop-datetime=‘2020-07-14 17:35:30‘  --database=数据库名称 > /www/server/202007142245.sql

6、执行恢复 

mysql -uroot -p密码 数据库名 < /www/server/202007142241upd.sql;

OK,我感觉已经大功告成,胜利在握了。

结果报错,说数据库已经存在...难道我的姿势不对?我得先删除数据库再操作???

好吧,备份当前数据库,删掉它。继续

mysql -uroot -p密码 数据库名 < /www/server/202007142241upd.sql;
继续报错,数据库不存在.....你想怎样....

于是我把脚本文件直接下载下来(600多M...),查看了一下内容,找到create database xxxxx 的时间,把第5步的导出脚本改成创建数据库之后

mysqlbinlog /www/server/data/mysql-bin.000004 --skip-gtids=true --start-datetime  "2020-07-10 17:38:46" --stop-datetime=‘2020-07-14 17:35:30‘  --database=数据库名 > /www/server/202007142245.sql

再继续第6步

mysql -uroot -p密码 数据库名 < /www/server/202007142245upd.sql;

还是报错,表xxxx已存在.....,我后面又改了几次方案,又发现主键等其他报错...此时我已是提心吊胆,颤颤巍巍,如履薄冰,哆哆嗦嗦

终于在第n个小时时候我找到了解决办法,如下

创建一个新的数据库,把导出的脚本文件(202007142245upd.sql)中有关于老数据库的名称 全部替换成 新的数据库名称 (这里用了notepad++),然后继续执行第6步(注意:数据库名称也换成新的)

mysql -uroot -p密码 新数据库名 < /www/server/202007142245upd.sql;

等了十几分钟,终于全程没报错的执行完毕。

接下来就简单多了,因为我只有一张表数据被恢复到之前的状态,而且恢复数据期间表的主键有拔高,我直接将新库里面那张渠道表所有数据导入老库替换就大功告成。

但是如果你出问题的数据多,而且表数据会不断变化,那么你只能先停止你的应用。关闭所有可能导致数据库变化的链接。然后再进行恢复操作了。

 

以上

参考文章:

https://juejin.im/post/5d39839d6fb9a07ee74322ff#heading-1

https://blog.51cto.com/lvnian/1699627

以上是关于记一次 MySQL出现“Lock wait timeout”错误的原因的主要内容,如果未能解决你的问题,请参考以下文章

记一次断电导致的mysql数据恢复问题

记一次zimbra服务器故障导致mysql起不来问题

记一次mysql的数据恢复

记一次线上MySQL数据库死锁问题

记一次网络原因导致的mysql连接中断问题(druid)

记一次压测中Mysql数据库异常分析过程