避免使用select *
反例:
select * from user where id=1;
在实际业务场景中,可能我们真正需要使用的只有其中一两列。此外,多查出来的数据,通过网络 IO 传输的过程中,也会增加数据传输的时间。最重要的一点是:select *
不走覆盖索引,会出现大量回表操作
正例:
select name,age from user where id=1;
小表驱动大表
1. join
MySQL 的 join 实现原理是:以驱动表的数据为基础,循环匹配被驱动表的记录
- left join 前面的表是驱动表,后面的表是被驱动表
- right join 后面的表是驱动表,前面的表是被驱动表
- inner join / join 会自动选择表数据比较少的作为驱动表
假设 a 表 10000 条数据,b 表 20 数据,sql 如下:
select * from a join b on a.id = b.id
这里有两个过程,b 表数据最少,查询引擎优化选择 b 为驱动表:
- 循环 b 表的 20 条数据
- 根据条件(a.id = b.id)在 a 表的 10000 数据中匹配,找到符合条件的数据后组装返回
对被驱动表的 join 字段,也就是 b.id 建立索引,匹配时可以走索引,此时查找次数为:20 * log10000
如果反过来选择 a 为驱动表,同样的过程,此时查找次数为:10000 * log20
显然 20 * log10000 要远远小于 10000 * log20
就算没有对被驱动表的连接字段建立索引,建立 20 次链接和建立 10000 次链接消耗的资源也有天壤之别
2. in
sql 如下:
select name from a where id in (select id in b);
MySQL 会先执行子查询,再执行主查询。这里相当于先获取 b 表的所有 id,然后循环所有 id,以每一个 id 为条件去执行主查询,因此子查询选择小表
3. exists
sql 如下:
select name from b where exists (Select * from a where a.id=b.id);
MySQL 会先执行主查询,再执行子查询。这里相当于先获取 b 表的所有数据,然后循环所有数据,取 b.id 为条件执行子查询,若有匹配则返回,因此主查询选择小表
索引优化
控制索引数量,因为对表的数据做增删改操作时,需要同步更改索引,消耗额外的性能。建议单表的索引数量尽量控制在 5 个以内,单个索引的字段数不超过 5 个
使用 explain 检查索引性能,参考文章:https://www.cnblogs.com/Yee-Q/p/18066673
索引失效常见场景:https://www.cnblogs.com/Yee-Q/p/18103308
批量操作
例如一批数据需要插入表,逐条插入需要建立多次请求数据库,此时可以选择批量插入
insert into order(id,code,user_id)
values(123,'001',100),(124,'002',100),(125,'003',101);
但不建议一次批量操作太多的数据,数据太多会导致数据库响应缓慢。建议每批数据尽量控制在 500 以内,如果数据多于500,则分多批次处理
高效分页
查询数据时,为了避免一次性返回过多的数据影响接口性能,一般会对查询接口做分页处理
MySQL 分页一般用 Limit 关键字:
select id,name,age from user limit 10,20;
随着页码增大,就会出现性能问题,因为 Limit 分页的原理是查出第一条到最后一条数据,再丢弃前面的数据。此时可以先记录上次分页最大的 id,然后根据 id 为条件查询,该方案要求 id 是有序的
select id,name,age from user where id > 1000000 limit 20;
还可以使用 Between 关键字实现分页,注意 id 要是连续的,不然会出现每页大小不一致的问题
select id,name,age
from user where id between 1000000 and 1000020;
用连接查询代替子查询
MySQL 执行子查询时,需要创建临时表,查询完毕后再删除临时表,产生额外的性能消耗,这时可以改成连接查询
子查询的例子如下:
select * from order
where user_id in (select id from user where status=1)
连接查询的例子如下:
select o.* from order o
inner join user u on o.user_id = u.id
where u.status=1
优化 Group By 语句
group by 关键字的主要功能是去重和分组,通常跟 having 一起使用,表示分组后再根据一定的条件过滤数据
使用 Having 例子如下:
select user_id,user_name from order
group by user_id
having user_id <= 200;
这种写法会先把所有的订单根据用户 id 分组后,再过滤用户 id 大于等于 200 的用户。分组是一个相对耗时的操作,我们可以先缩小数据的范围,再进行分组
select user_id,user_name from order
where user_id <= 200
group by user_id;
使用 where 条件分组前,把多余的数据过滤掉,这样分组效率就会更高一些
选择合理的字段类型
char 表示固定字符串类型,存储空间是固定的。varchar 表示变长字符串类型,存储空间会根据实际数据的长度调整。如果是长度固定的字段,比如用户手机号,可以定义成 char 类型,长度是 11 字节,如果是用户名称,使用 char 类型定义长度太长或太短都有可能出现问题,建议使用 varchar 类型
选择字段类型时,应该遵循这样的原则:
- 能用数字类型,就不用字符串,因为字符的处理往往比数字要慢
- 尽可能使用小的类型,比如:用 bit 存布尔值,用 tinyint 存枚举值等
- 长度固定的字符串字段,用 char 类型
- 长度可变的字符串字段,用 varchar 类型
- 金额字段用 decimal,避免精度丢失问题