MySQL 如何优化

1,995 阅读4分钟

优化总结

  • 做索引
  • 使用 EXPLAIN 分析,type至少要达到range级别,杜绝出现all级别
  • 最左前缀原则
  • 不要使用 select *
  • 尽量用union all代替union
  • 使用合理的分页方式以提高分页的效率, 如加索引字段作为查询条件
  • 不建议使用%前缀模糊查询
  • 避免在 where 子句中对字段进行 null 值判断(会导致索引失效)
  • 当只需要一条数据的时候,使用limit 1, EXPLAIN中type列达到const类型
  • 如果排序字段没有用到索引,就尽量少排序
  • 如果限制条件中其他字段没有索引,尽量少用or
  • 分段查询,使用between缩小查询访问
  • 必要时可以使用force index来强制查询走某个索引
  • 避免隐式类型转换
  • join优化,利用小表驱动大表
  • 分库分表
  • 做主从,读写分离

EXPLAIN

列名 描述
type 针对单表的访问方法,至少要达到range级别,杜绝出现all级别
key 实际上使用的索引,如果没有选择索引,值是NULL
ref 当使用索引列等值查询时,与索引列进行等值匹配的对象信息
rows 预估的需要读取的记录条数
Extra 额外的信息

最左前缀原则

比如设计的联合索引是 name_birthday_phone,那么我们在sql中where后面需要按照这个顺序

// 索引起效
SELECT * FROM person_info WHERE name = 'Owater' AND birthday = '2020-04-07';

// 索引失效
SELECT * FROM person_info WHERE birthday = '1990-09-27';

那最左前缀原则有什么作用呢

  • 充分利用索引,节省索引空间
  • 建立了name_birthday_phone,也就意味着不需要单独再建立索引name和索引name_birthday

那为什么不按照这个顺序,索引就失效了呢?

  • 因为B+索引是有序排序,先按照name排序再按照birthday、phone依次排序,所以突然从birthday开始查找,是无法查找的到

匹配列前缀

通常情况下一般都不太建议使用LIKE,那么该如何正确使用LIKE呢?

首先,我们需先理清楚一个字符串索引在B+树种是怎么存储?

Aaple
Apple
Azple
..
Baple
Bbple
  • 从以上排序,得知一个字符串索引是按照字母的大小进行排序
  • 那么程序查找自然也是需要按照这个顺序
// 索引起效
SELECT * FROM user WHERE name LIKE 'As%';

// 索引失效,%开始,程序不知从哪处开始查找
SELECT * FROM user WHERE name LIKE '%As%';

为什么不能使用 select *

SELECT * FROM user WHERE name > 'Owater' AND name < 'Water';

如上sql,即使是使用了索引,但是效率还是低,因为这过程发生了回表

  1. 从索引 name_birthday_phone 对应的B+树中取出name值在Owater~Water之间的用户记录。此时是访问二级索引使用顺序I/O
  2. 发现查询列表是 ==*==,则需到 聚集索引 中查找所有数据,这是所谓的回表。此时访问聚集索引使用随机I/O,顺序I/O比随机I/O的性能高很多。

尽量用union all代替union

  • union 将结果集合并后再进行唯一性过滤操作,这就会涉及到排序,增加大量的CPU运算。
  • union all 则是将两个结果集进行合并,会存在重复的情况。

使用合理的分页方式以提高分页的效率

假设一张表100W条数据,如果只是单独使用limit,随着偏移量越大效率越低,因为检索需从某一个偏移量到记录集的结束所有的记录行

// 效率低
select id, name, phone from user limit 100000, 20

// 优化如下,可以采用id来限制下一页的起点
select id, name, phone from user where id > 100000 limit 100000, 20

谨慎使用范围查询

比如between,>,<等条件时,如果对多个列同时进行范围查找的话,只有对索引最左边的那个列进行范围查找的时候才能用到B+树索引。

SELECT * FROM user WHERE name > 'Owater' AND name < 'Water' AND birthday > '2020-04-09';
  • 查询name的时候,会用到索引,而birthday列用不到索引,因为只有name值相同的情况下才能用,而这个查询中通过name进行范围查找的记录中可能并不是按照birthday列进行排序的。

分段查询

当扫描的行数成百万级以上的时候,可以通过程序循环遍历,分段去数据库查找数据,再将结果集进行合并,这样可以减负数据库单次执行的性能消耗。

区分in和exists

  • in 是先执行子查询,适合于外表大而内表小的情况。
  • exists 是以外层表为驱动表,先被访问,适合于外表小而内表大的情况。