发现的一些问题
问题1
在过去的半年时间里,研发团队内部尝试抓了一波儿慢查询SQL跟进处理率。发现有些同学对于慢查询处理的思路就是看看有没有用到索引,没有用到就试图加一个,实在不行就甩锅给这种情况是历史设计问题或者自行判定为用户特殊操作下触发的小概率事件,随即便申请豁免掉...
这样其实问题没有根本上解决。
问题2
还有就是网络上经常可以看到一些类似这样的文章:
“慢SQL性能优化大全”
“慢SQL性能优化看这篇就够了”
......
其实内容大同小异,要么建议加索引,要么建议重写SQL....
怎么说呢?知识点是对的,但不全面,这个很容易误导新同学,哈哈哈。
本文初衷
在业务项目发展过程中,我们常常会面对要处理 MySQL 慢查询问题,那我们应该如何分析解决问题呢?
部分同学在处理MySQL慢查询时候主要思路是加索引来解决,确实加索引是一个很好的解决问题的手段,但不是全部。既然慢查询作为问题,那就需要明确问题发生原因,和解决问题路径分析, 授人以鱼不如授人以渔,让我们一起来解锁 下MySQL处理慢查询的正确姿势。
本文计划主要让大家搞明白查询SQL为什么会变慢
废话不多说,直接开干~
写在前面
在业务项目发展过程中,我们常常会面对要处理 MySQL 慢查询问题,那我们应该如何分析解决问题呢?
部分同学在处理MySQL慢查询时候主要思路是加索引来解决,确实加索引是一个很好的解决问题的手段,但不是全部。既然慢查询是问题,那就需要明确问题发生原因,和解决问题路径分析。我们一起来get下MySQL慢查询的正确姿势。
本文主要内容包括:
1、查询SQL执行到底经历了什么?
2、查询SQL为什么会慢?
1. 查询SQL执行到底经历了什么?
首先需要明确:一个查询SQL的执行到底经历了什么?
数据库执行SQL的大致流程如下:建立与MySQL服务器连接(基础)
客户端发送查询SQL到数据库,数据库验证是否有执行的权限
MySQL服务器先检查查询缓存,如果命中了缓存,则立即返回存储在缓存中的结果,否则继续流转;
MySQL服务器语法解析器,进行词法与语法分析,预处理
流转至查询优化器生成执行计划
根据生成的执行计划,调用存储引擎暴露的API来执行查询
将查询执行结果返回给客户端
关闭MySQL连接具体执行过程可能会因MySQL服务器具体配置和执行场景有一些差异。
1)如未开启应用查询缓存,则直接忽略查询缓存的检查;
2)执行过程中,如同时对于被扫描的行可能加锁,同时也可能会被其他sql阻塞
2. 查询SQL为什么会慢?
我们可以把查询SQL执行看做是一个任务的话,那它是由一些列子任务组成的,每个子任务都存在一定的时间消耗。通常情况下,导致慢查询最根本的问题就是需要访问的数据太多,导致查询不可避免的需要筛选大量的数据。
面对慢查询,我们需要注意以下两点:
1)查询了过多不需要的数据
2)扫描了额外的记录
2.1 查询了过多不需要的数据
MySQL并不是只返回需要的数据,实际上会返回全部结果集再进行计算。
尤其是多表关联查询 select * 的情况,我们是不是真的需要全部的列呢?如果不是,那我们直接指定对应字段就好了。
例如我们要查询用户关联订单下的商品信息,如下所示:SELECT *
FROM users
LEFT JOIN orders ON orders.user_id = users.user_id
LEFT JOIN goods ON goods.good_id = orders.good_id
WHERE users.name = 'zhangsan';复制代码
这将返回三个表的全部数据列,可以调整为仅取需要的列:SELECT goods.title, goods.description
FROM users
LEFT JOIN orders ON orders.user_id = users.user_id
LEFT JOIN goods ON goods.good_id = orders.good_id
WHERE users.name = 'zhangsan';复制代码
取出全部列,会让优化器无法完成索引覆盖扫描这类优化,还会为服务器带来额外的I/O、内存和CPU的消耗。
2.2 扫描了额外的记录
此种情况大部分属于索引应用不当造成的(包括:应该建的索引没有建,或者未应用到最佳索引)。
示例表结构如下:CREATE TABLE `test_table` (
`name` varchar(32) DEFAULT NULL,
`desc` varchar(32) DEFAULT NULL,
`age` int(16) DEFAULT NULL,
`id` bigint(11) DEFAULT NULL,
KEY `idx_age` (`age`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4复制代码
存在索引 `idx_age` 的情况下,查询执行计划结果展示如下:EXPLAIN SELECT * FROM test_table WHERE age = 10;复制代码
预估访问1行数据即可命中数据,如删除有效索引 `idx_age` 后则会变成全表扫描(ALL),预估需要扫描121524条记录才能完成这个查询,如下图所示:
总结
根据梳理 MySQL中的 SQL执行过程我们发现,任何流程的执行都存在其执行环境和规则,其实产生慢SQL的本质是:我们没有按照数据库的要求方式来执行SQL。
主要导致慢查询最根本的问题就是需要访问的数据太多,导致查询不可避免的需要筛选大量的数据。
限于文章篇幅,同时为了大家更好的阅读体验,后面会连续产出系列文章:
MySQL慢查询(中)
主要内容包括 如何定位慢查询问题和几种实用解决方案介绍
MySQL慢查询(下)
主要内容包括 高性能查询难题优化内容点总结
最后,欢迎大家持续关注~
Thanks for reading!