记一次MySQL线上查询慢问题

一、问题描述 1、生产环境
MySQL5.7.10Connect Timeout=45超时时间设置为45s,启用连接池,最大连接池设置为2000,数据库服务器2台,读写分离。
2、问题呈现
今天早上一个业务接口突然出现问题,经查询,5台服务器都不可访问,日志为查询数据库超时Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding. ---> System.TimeoutException: Timeout in IO operation
二、原因探寻 既然超时,那就要查询该接口到底是哪里慢了。拿到该接口执行的sql,直接在dbForge Studio for MySQL执行,在从库上(客户端只有从库的读权限,生产上使用的主库)执行时间为3.78s,虽然慢,但是执行时间远小于设置的超时时间。
【记一次MySQL线上查询慢问题】怀疑是否是由于数据库连接池满了,连接数达到上限。于是查询数据库的连接数,小于峰值。同时异常日志是IO超时,而不是超过最大连接数。那为什么会超时呢?
组内同事联系运维同事,让他帮忙把该SQL在主库查询,竟然需要22s。运维同事说,在9:40~10:00这时间段,主库压力太大,导致超时。从库的压力较小。运维建议使用从库。
同时该查询语句也属于慢查询,准备优化。
查看执行计划:

记一次MySQL线上查询慢问题
文章图片
explain.png p表进行全表扫描Full Table Scanp表建立的是联合索引,但是查询的字段中有联合索引之外的字段,导致全表扫描。好,那就去查询去除不包含在索引中的字段,重新查询,查看执行计划:

记一次MySQL线上查询慢问题
文章图片
explain (1).png
p表为全索引扫描Full Index Scan。但是查询并没有变快。

与全表扫描比较,索引全扫描有如下两好处:1:扫描的块更少,因为索引只储存部分字段的数据;2: 索引是顺序存储的,在某些条件下,可以避免额外的排序操作
把原始的查询SQL(包含不在联合索引中的字段)放在测试数据库执行,执行时间不到200ms,查看执行计划:

记一次MySQL线上查询慢问题
文章图片
explain (2).png
p表没有全表扫描Full Table Scan,而是Non-Unique Key Lookup非唯一索引查询。那这就奇怪啦?为什么呢?
难度是测试库和生产环境索引不一致,排查了下,完全一致。难道是由于数据量不一致导致的?由于把生产库所有相关表的表结构及数据导入到测试服务器新建的数据。


查看执行计划 记一次MySQL线上查询慢问题
文章图片
explain (3).png 查询时间为78ms
那么问题来了,这么大的差异,是由于什么导致的呢?数据库的设置?
联系运维同事,让帮忙比较下测试库与生产库数据库的设置。
经查询,生产库优化策略禁止derived table合并到外层的Queryderived_mergeset optimizer_switch='derived_merge=off'
在测试环境禁止止derived table,执行SQL
查看执行计划:

记一次MySQL线上查询慢问题
文章图片
explain (4).png
执行时间3.92s,p全表扫描Full Table Scan。终于找出来了,是由于生产环境与测试环境优化方式不一致导致的。而前段时间是由于测试前段时间迁移了数据库,所以derived_merge默认开启。
三、解决方式 既然知道问题所在,那就解决了。
  • 目前主从同步,平均在秒内可完成,由于该接口对数据的实时性要求不是很高,可使用从库查询数据。
  • 上面我们已经知道由于生产环境未开启SQL优化derived_merge,那最直接的方法当然是开启,set optimizer_switch='derived_merge=on'。问题来了,在MySQL 5.7.10版本启用合并,会将子查询中的SQL合并到外部查询中,会发生ER_UPDATE_TABLE_USED错误,那生产库主库暂时不能启用优化。解决方式是:从库启用查询优化。
四参考 Changes Affecting Upgrades to MySQL 5.7
Optimizing Derived Tables and View References
索引全掃描

    推荐阅读