背景
压测过程中测试小伙伴反映某个页面长时间loading无法打开,接下来我们排查一下,既然是压测环境,那么就需要排除服务器资源层面的因素,现在考验的就是在系统资源不足时系统的情况,那么我们就直接从代码层面开始排查。
排查思路
在浏览器中打开F12,复现问题,确定那个接口存在性能问题,接下来需要确定接口内部的实现逻辑是什么样,做了哪些事情,调用了哪些服务,分析接口存在性能问题的根本原因,在排查过程中,着重留意是否在for循环中执行数据库操作、调用第三方接口、调用第三方接口耗时,执行的SQL是否有性能问题。随后确定优化方案,验证效果发版。
MyBatis 标签数据量较大时存在性能问题,在接口分析中按SQL性能问题处理。
随版本迭代以及需求变更,接口中的逻辑可能越来越臃肿,关联查询的表越来越多,查询的数据量越来越大,为保证业务闭环,新增与更新可能要同时操作N张表,以上均是影响接口性能的因素。
排查过程
复现排查过程中发现,有一条慢SQL执行时间过长,导致查询的对应表产生了死锁,那么这就是接口长时间pending的愿意了。
解决方案
由于对应表处于死锁状态,那么首先需要将死锁的表释放,杀掉对应的查询进程。那么需要执行以下命令:
代码语言:javascript复制#以下命令需要同时执行
show OPEN TABLES where In_use > 0;
SHOW PROCESSLIST;
SELECT * FROM information_schema.INNODB_TRX;
SELECT * FROM information_schema.INNODB_LOCKs;
#根据进程号kill
kill pid
解除锁后,使用explain
语句查询执行计划,确认由于业务变更,关联查询表过多,在数据量较大时导致SQL性能极具下降,该SQL所在的事务处理时间过长,导致其他事务等待锁的时间过长,最终导致死锁。
阿里巴巴开发手册中建议:查询时关联表不要超过三张。
基于此建议,我们将原SQL语句进行拆分,以避免在造成长事务造成数据库表死锁导致接口长时间pending的问题。经过测试问题得解。
知识扩展-如何避免死锁
引用自: 什么是MYSQL死锁?如何避免死锁?
MYSQL造成死锁的原因
- 并发访问:当多个事务并发访问同一资源时,容易出现死锁。
- 事务请求资源顺序不当:如果多个事务对相同的资源加锁的顺序不同,也容易引发死锁。
- 锁超时:如果一个事务持有锁的时间过长,例如有一个事务处理时间太长了,就可能导致其他事务等待锁的时间过长,最终导致死锁。
- 锁粒度过大:如果锁的粒度过大,例如一个事务锁住几十条数据,甚至表锁,那么在事务并发访问同一资源时,容易造成死锁。
如何分析死锁
- 错误日志: MySQL的错误日志中记录了死锁信息,包括死锁发生的时间、持有锁的线程、等待锁的线程等。
- SHOW ENGINE INNODB STATUS:可以使用该命令查看InnoDB引擎的状态信息,包括死锁的详细信息。其中,LATEST DETECTED DEADLOCK部分列出了最近的死锁事件,包括持有锁和等待锁的事务ID以及锁的详细信息。
- information_schema表: MySQL提供了一些用于查看锁和事务状态的系统表。例如,information_schema.INNODB_TRX表包含当前执行的事务列表,information_schema.INNODB_LOCKS表包含当前的锁列表,information_schema.INNODB_LOCK_WAITS表包含当前的锁等待列表等。
- 如果使用阿里云之类的云数据库,通常监控系统可以查询死锁信息。
如何解决MYSQL死锁
- 重试:当出现死锁时,可以重试该事务,让其重新尝试执行。重试的次数可以限制,超过一定次数后可以选择中止该事务。
- 加锁顺序:尽量保证不同的事务对锁的访问顺序一致,这样可以避免死锁的产生。比如,所有事务都按照相同的顺序获取锁,或者按照相反的顺序获取锁。
- 缩小事务范围:可以将一个大事务拆分成多个小事务,每个小事务只涉及部分数据,这样可以减少死锁的可能性。
- 提高隔离级别:如果业务允许,可以将隔离级别提高到SERIALIZABLE,这样可以保证读取数据时的一致性,并且避免了读取到其他事务正在修改的数据,减少死锁的可能性。
- 减少锁定时间:尽量减少事务占用锁的时间,比如可以在需要修改的时候再获取锁,而不是一开始就获取锁。
- 使用索引:合理的索引设计可以避免全表扫描,减少锁的竞争和等待时间,从而减少死锁的发生。
思考与建议
- 任何排查方式都不如在写代码时多一些思考,多一个极限条件设想。我们不仅要想办法解决问题,更需要尽量避免可能出现的问题。
- 编程要养成“分而治之”的习惯,以避免和解决大数据量下水桶最短板导致的问题。