写在前面
优化慢SQL,这是在工作或者面试中都不可避免的问题。这篇文章我们就来讲讲慢SQL的优化的一些方法!
1. 升配
最简单的一步就是升配!!当然在降本增效的当下,很难能将这种单子审批下来了!
2. 索引
MySQL一般会使用innodb作为存储引擎,而innodb存储引擎的索引结构为B+树,索引有啥用?
mysql的数据是存储在磁盘里的,而磁盘的io的随机读写是很慢的,因为需要不断的寻道,而索引是为了加速查询的速度。
B+树的结构如下:叶子结点存储数值,非叶子结点存储指针和键值
。
如果我们不加索引,MySQL就会进行全表扫描!如果加了索引,那么B+树就会进行走索引!那么就会少走很多的磁盘io。
现在我们知道了为什么建索引,但并不是所有的字段都需要或者都需要建立索引的,因为索引也是占空间的,而且索引是有时候也会失效的!
也会影响到更新速度,这被称为过度索引了,那么什么字段适合建立索引?什么字段不适合建立索引呢?
3. 什么字段适合建立索引?
大体可有以下四种情况:
- 针对查询比较频繁字段做索引,并且尽量选择区分度高的列作为索引,
尽量建立唯一索引,区分度越高,使用索引的效率越高。
- 尽量使用
联合索引,减少单列索引,查询时,联合索引很多时候可以索引覆盖,避免回表,提高查询效率
。 - 索引应该建立在小字段上,对于大的文本字段不要建立索引。
- 要控制索引的数量,索引过多,维护索引结构的代价很大,会影响
增删改的效率
B+树的本质是一颗平衡树,当底层的数据量过大的时候,索引就会向上分裂,再合并,从而形成层。
如果我们对性别这一类的字段建立索引,就会出现以下这种情况,这种情况其实和扫全表没啥区别了,反而还浪费了空间。
而如果是对一些重复度不高、唯一性很强
的字段,比如用户唯一id这种,建立索引,那么能很快速的找到这个数据。比如以下这种索引
3. 什么时候索引会失效?为什么?
3.1 使用了不等号(<> 或 !=)
SELECT id,username FROM user WHERE age <> 18;
索引适合用于查找具体值或范围值
,如 =, <, >, BETWEEN 等操作,而 <> 或 != 运算符要求 MySQL 查找所有不等于某个值的记录,范围模糊且不能明确定位
,索引不能高效定位这些记录。`
3.2 OR 连接多个条件
SELECT id,username FROM user WHERE user_id = 1 OR age = 18;
当使用 OR 连接多个条件时,MySQL 可能
无法有效地使用索引。为什么是可能?因为OR走索引与否,还和优化器的预估有关,就算连接条件都设置了索引,也可能因为回表导致索引失效
。
- 索引 + OR + 无索引的列:会先走索引列,但无索引的列会进行全表扫描,所以还不如不走索引,直接都全表扫描完事。
- 索引 + OR + 索引,那么可能走索引,也可能不走索引。
MySQL 会尝试分别使用每个条件的索引,然后将结果合并,往往效率较低。解决方法其实很简单,只需要将OR拆开,用UNION、UNION ALL拼接起来。
改成
SELECT id,username FROM user WHERE user_id = 1
UNION
SELECT id,username FROM user WHERE age = 18;
3.3 对索引列进行函数操作
当对索引列应用函数(如 UPPER(), YEAR(), CONCAT() 等),MySQL 不能直接在索引中查找优化后的数据,而是必须先计算函数结果,然后再去查找匹配的行
。这会使得索引失效。
eg:
SELECT id,username FROM user WHERE YEAR(birthdate) = 2024;
这里YEAR(birthdate) 是一个函数,MySQL 不能直接利用 birthdate 列上的索引进行查询,而需要先计算 YEAR(birthdate),导致索引失效。
3.4 隐式类型转换
当查询条件中的数据类型与表字段的数据类型不匹配时,MySQL 会进行隐式类型转换。在这个过程中,可能导致索引失效,因为索引是基于特定的数据类型创建的,类型转换后,可能无法高效利用索引。
eg:假设age是整数类型,但是却使用字符串类型
SELECT * FROM user WHERE age = '20';
MySQL 需要在查询时转换 ‘20’ 为整数类型,可能会导致索引无法使用。 某个电商平台就有这么一个类似的bug,导致下单超时崩盘30分钟。
3.5 范围查询与其他条件混合
如果一个复合索引包含多个列,并且查询中有范围条件,如 BETWEEN, >, <,MySQL 会优先根据范围条件来 定位数据,而忽略其他条件。这种情况下,索引可能只会应用于范围条件的列,其他列无法有效利用索引。
示例:
SELECT id,username FROM user WHERE birthdate > '2024-01-01' AND age = 18;
假设有一个复合索引 (birthdate, age),由于 birthdate > ‘2024-01-01’ 是一个范围查询,MySQL 只会使用 birthdate 列的索引,而 age 列则可能无法使用索引。
解决方法:尽量避免范围查询和精确查询混合在同一个条件中,或者重新设计索引顺序以适应查询的特点。
eg:改成复合索引为 (age,birthdate) 并且SQL语句如下:
SELECT id,username FROM user WHERE age = 18 AND birthdate > '2024-01-01';