一条简单 SQL 执行耗时超 1000ms,问题解决全过程!

点击上方“Java精选”,选择“设为星标”

别问别人为什么,多问自己凭什么!
下方留言必回,有问必答!
每天 08:00 更新文章,每天进步一点点...

大概过程

在测试环境Docker容器中,在跨进程调用服务的时候,A应用通过Dubbo调用B应用的RPC接口,发现B应用接口超时错误,接着通过debug和日志,发现具体耗时的地方在于一句简单SQL执行,但是耗时超过1000ms。

通过查看数据库的进程列表,发现是有死锁锁表了,很多进程状态status处于'sending data',最后为锁住的表添加索引,并且kill掉阻塞的请求,解除死锁,服务速度恢复正常。

下面记录的是大致排查过程:

通过观察业务代码,确认没有内存溢出或者其它事务问题,于是只能考虑Docker环境的数据库和jvm底层详情了。

使用Druid监控SQL执行状态

通过日志,发现有一句SQL严重超时,一句简单SQL,原本是批量插入多条记录,为了定位问题,测试时Mybatis只插入一条记录,但即便如此,还是耗时10秒

80108b9649290825323930fda6c098bd.png

于是打算使用阿里巴巴的数据库连接池Druid进行监控,监控SQL效果如下:

62d7fc18658413dbb0487781579c10f5.png

在SQL监控Tab中,可以看到执行SQL的具体情况,包括某条SQL语句执行的时间(平均、最慢)、SQL执行次数、SQL执行出错的次数等

上面显示的是正常情况下,时间单位是ms,正常的SQL一般在10ms之内,数据量大的控制在30ms之内,这样用户的使用体验感才会良好。

所以说之前的1000ms,是不可接受的结果。考虑跳槽或者找工作的朋友推荐看面试题集,公众号Java精选,回复Java面试,获取全新面试题资料。

通过JMC远程监控Tomcat

J****MC(java mission control)是jdk自带的一个监控工具,在jdk的bin目录下(java大法好,该目录下有很多实用的工具)。

此处加了一个tomcat无验证模式:

#在tomcat的conf目录下的catalina.sh增加如下java启动参数:
-Dcom.sun.management.jmxremote=true
-Dcom.sun.management.jmxremote.port=8888
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=false
-XX:+UnlockCommercialFeatures -XX:+FlightRecorder

下面是自己本地调试的截图

2f267981f818882aec8d008dc00ca5e2.png

然后打开jmc,创建一个JMX连接,输入对应的ip和JMX端口。

接着可以设定一段时间内的飞行监控,监测这一分钟内jvm具体参数

当时调试的时候,发现内存使用、CPU占用率、线程状态也挺正常的,没有发现明显的异常错误,效果如下图:

c8bc86f5017e13b2b97f713e6b2e5be9.png

唯一比较耗时的是在代码tab页中,**当时发现了大量的I/O,比上图的比例还高,当时大概占了80%,查看调用树,很多循环tcp socket连接,**考虑到应用中本来就有很多需要io以及netty也需要tcp连接,所以大概排除了jvm虚拟机的问题,然后就去排查MySQL的问题。

排查MySQL

在了解MySQL锁概念的时候,由于现在使用的比较多的是InnoDB,所以可以着重看看InnoDB锁问题。考虑跳槽或者找工作的朋友推荐看面试题集,公众号Java精选,回复Java面试,获取全新面试题资料。

直接执行SQL语句 通过DEBUG代码,从mybatis中取出映射后的SQL语句,在MySQL客户款直接执行SQL和Explain查看执行计划,速度都很快,排除了SQL语句的问题。

查看MySQL线程列表

show processlist;
a4d7d2520a95c2df5fffa736541431c4.png

从图中可以看出,有些线程的状态处于sending data,查阅资料:所谓的“Sending data”并不是单纯的发送数据,而是包括“收集 + 发送 数据”。

然后后面一列info显示的是具体信息,是查询用来生成主键ID的函数,之前速度都很快,为啥突然就这么慢呢,于是回过头去查看该函数:

select next_value into ret_val from `xxx` where table_name=tableName for update;

update `xxx` set
        current_value=current_value+step,
        next_value=next_value+step
    where table_name=tableName;

select for update,给这个表加了排它锁,阻止其它事务取得相同数据集的共享读锁和排他写锁,同时,这个序列表表中,用来检索的字段没有加索引,在InnoDB行锁机制中:

d9f43633a2c396046fcc03c23e24461c.png

由于MySQL的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是如果是使用相同的索引键(在我们的场景中,就是查询时用到的table_name),是会出现锁冲突的。

所以了解到其它团队因为查询这个表产生事务问题,造成死锁,这个序列表被锁住了。

由于这个自增序列表每个团队都在使用,所以当时测试环境中,经常有dao层超时错误,最终将这些阻塞的线程kill掉,为序列表加了索引,解决了问题。

小结

下次遇到MySQL执行耗时的情况,排除了代码问题之后,要去看数据库是否有死锁的情况存在,观察有没有被阻塞的线程,排查被阻塞的线程具体info,定位到具体问题。

作者:VipAugus

juejin.cn/post/6844903861803548685

精品资料,超赞福利!

>>Java精选面试题<< - 小程序,3000+ 道面试题在线刷,最新、最全 Java 面试题!

eb3b9d7aaf7ffa12da87d120b7849072.png

期往精选  点击标题可跳转

笑出腹肌的注释,都是被代码耽误的诗人!

想自己搭建个人服务器,永久运行网站?一个 U 盘就够了!

每日 CRUD?两年滴滴和入职头条的后端开发经验分享!共勉!

Java 8 一行代码解决了空指针的问题,太厉害了!!!

别乱提交代码了,看下大厂 Git 提交规范是怎么做的!

“80 后的人,真的该退出 IT 行业了”,某 IT 公司领导言论惹争议!

面试官 | 写 if 时不带 else,你的代码会更好!

yyds!程序员国庆长假去哪里,既小众又便宜?

我把 Spring Boot 的 banner 换成了美女,老板说工作不饱和,建议安排加班

文章有帮助的话,在看,转发吧!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值