慢SQL
慢SQL通常是指执行耗时超过预设阈值的 SQL 语句,该阈值一般由long_query_time参数控制,默认值为10秒。

定位
- 可以通过在配置文件my.cnf的[mysqld] 下添加以下配置来开启慢查询日志(重启后依然生效):
[mysqld] slow_query_log=1 //开启慢查询日志slow_query_log_file=/var/lib/mysql/mysql-slow.log //慢查询日志存放路径 long_query_time=3 //慢查询阈值log_output=FILE
也可以通过在mysql客户端执行命令临时开启(重启后失效),如下:
SET GLOBAL slow_query_log = ‘ON';SET GLOBAL slow_query_log_file = ‘/var/lib/mysql/mysql-slow.log';SET GLOBAL long_query_time = 3;
分析
通过执行 EXPLAIN,在对应SQL前加上 EXPLAIN 后,即可查看该SQL的执行计划,示例如下:
id select_type table partitions type possible_keys key key_len ref rows filtered Extra ------ ----------- ------- ---------- ------ ------------- -------- ------- ------ ------ -------- ------------- 1 SIMPLE student (NULL) index (NULL) name_age 68 (NULL) 10 100.00 Using index
分析执行计划时,主要关注 type、key、rows、extra
type
表示本次执行该SQL时所使用的访问类型或索引扫描方式
索引类型的效率:
NULL > system > const > eq_ref > ref > ref_or_null > index_merge > range > index > ALL,这是常见的性能排序顺序。
NULL表示该SQL不需要实际访问表,或者获取的是索引的最大值或最小值(只需访问索引叶子节点的左端或右端)
system表示该SQL访问的表中只有一行记录
const表示该SQL使用主键索引或唯一索引,通常只需一次索引查找即可定位记录
eq_ref表示该SQL使用了join关联查询,并且能够通过主键索引或唯一索引定位唯一记录
ref表示可以通过非唯一索引查找到匹配记录
ref*_or_null *表示在ref的基础上,索引列还支持null值匹配
index_merge表示使用了多个索引进行组合查询(通常不是联合索引)
range 表示使用索引列进行范围条件查询,如
=, <>, >, >=, <, <=, IS NULL, <=>, BETWEEN, IN
index 表示使用索引进行了全索引扫描
all 表示未使用索引,直接进行全表扫描
key
表示实际命中的索引名称(即建立索引时定义的名称),如果没有使用索引则为NULL
rows
表示mysql预估该SQL通过索引访问时需要读取到server层的数据行数。对于同一条SQL来说,不同索引下该值越小,通常说明索引效果越好
filtered
表示预估读取到server层后,没有被过滤掉的数据行比例,即 n/rows*100%
extra
表示mysql执行过程中的额外优化信息
using index:使用了覆盖索引using where:需要回表后再根据where条件进行数据过滤using index condition:使用了索引下推using temporary:使用了临时表,常见于聚合函数、子查询等还需要进一步处理数据的SQLusing filesort: 对结果集进行了排序,但没有使用到索引(索引失效)或缺少对应索引
索引优化
最左前缀匹配
建立联合索引时,索引字段顺序通常需要结合使用频率、字段区分度以及范围查询(可能导致后续索引失效)来综合判断
如 SQL:
select * from student where score=60 and finished_time >'2000-10-10'
建立索引
idx_score_finished(score,finished)
注:具体仍需结合实际业务场景进行判断
索引覆盖
对于高频查询、返回字段较少的SQL,可以考虑建立联合索引来实现覆盖索引,从而减少回表次数,提升查询性能
如 SQL:
select name,score from student where finished_time ='2000-10-10'
建立索引
idx_finished_name_score
索引下推
可以针对查询条件建立合适索引,以减少回表到server层后再过滤的数据行数。需要注意的是,当查询条件中的索引无法生效,或者根本没有可用索引时,数据只能传到server层再做过滤处理,这往往会增加SQL执行成本。
如 SQL:
select score from student where name ='stu' and finished_time ='2000-10-10'
建立索引
idx_name_finished
表结构优化
数据类型
如果可以确定该列不会为NULL(并且有默认值),设置not null能够节省部分存储空间,同时有助于提升查询效率
数字类型
- 根据数值范围选择tinyint、int、bigint,避免存储空间浪费。若确认数值不存在负数,优先选择unsigned
- double类型在计算时可能存在精度丢失问题,可根据业务改为int类型,由业务层处理小数,或者直接使用decimal
字符类型
- 尽量避免滥用text类型
- 对于定长字符或长度差异不大的字段,使用char有时更合适,能够节省一部分额外存储开销(省去varchar的len部分)
- varchar长度应根据业务需求合理设计,过长会导致行数据读入内存时消耗更大
日期类型
- 如果时间范围在1970-2038之间且没有特殊要求,可优先使用timestamp,在相同精度下其存储空间约为datetime的一半
- 如果只需要保存到日,也可以使用datetime,只需3字节
字符编码
如果对字符兼容性要求不高,可以不一定选择utf等相对冗余的编码方式,建议按实际需求选择字符集
注:存在联表关系的表中,表间关联字段的字符集应保持一致,避免关联时因字符集不同而导致索引失效
范式与反范式平衡
可以根据业务场景适当进行反范式设计,也就是增加冗余字段,以减少关联查询开销
如: product表 kind表 可在product表上冗余kind_name字段
像text这类大字段会影响数据行加载到内存页的速度(大字段可能发生行溢出,带来额外磁盘IO),可以考虑拆分到单独表中,再通过关联方式读取
SQL优化
- 按需SELECT,避免使用SELECT*,减少网络IO和带宽消耗
- 索引字段要避免MySQL隐式类型转换,例如bigint类型的id应传int64而不是字符串,防止索引失效
- like查询尽量避免把通配符放在左侧,否则不符合最左前缀匹配原则,容易导致索引失效
- 避免在索引列上使用mysql内置函数(server层处理),否则可能导致索引无法命中
- 避免对索引列进行计算或运算操作
- 索引列做等值查询时,优先使用=,而不是<>
- 多表操作如join、in等,尽量使用小表驱动大表的写法,减少关联数据量
- 批量操作建议在业务层分批处理,再通过SQL批量执行,减少高并发插入带来的锁竞争
- 合理使用limit减少单次返回的数据行数和回表行数,从而降低网络IO与SQL执行时间
- 深分页可以采用延时分页优化:先查出符合条件的id并limit,再回主表关联查询,减少不必要的二次回表成本
总结
以上内容是关于慢SQL定位、慢查询分析以及MySQL优化的一些个人经验总结,希望能为大家排查和优化慢查询提供参考,也欢迎大家继续支持本站。
