04SQL索引优化(一)

SQL索引优化(一)

  • 示例表
CREATE TABLE `employees` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(24) NOT NULL DEFAULT '' COMMENT '姓名',
`age` int(11) NOT NULL DEFAULT '0' COMMENT '年龄',
`position` varchar(20) NOT NULL DEFAULT '' COMMENT '职位',
`hire_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '入职时间',
PRIMARY KEY (`id`),
KEY `idx_name_age_position` (`name`,`age`,`position`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='员工记录表';

 INSERT INTO employees(name,age,position,hire_time) VALUES('LiLei',22,'manager',NOW());
 INSERT INTO employees(name,age,position,hire_time) VALUES('HanMeimei', 23,'dev',NOW());
 INSERT INTO employees(name,age,position,hire_time) VALUES('Lucy',23,'dev',NOW());

 ‐‐ 插入一些示例数据
 drop procedure if exists insert_emp;
 delimiter ;;
 create procedure insert_emp()
 begin
 declare i int;
 set i=1;
 while(i<=100000)do
 insert into employees(name,age,position) values(CONCAT('zhuge',i),i,'dev');
 set i=i+1;
 end while;
 end;;
 delimiter ;
 call insert_emp();

常见例子

1.联合索引第一个字段用范围不会走索引

EXPLAIN SELECT * FROM employees WHERE name > 'LiLei' AND age = 22 AND position ='manager';

image-20240103193155858

  • 联合索引的第一个字段就用范围查找不会走索引,mysql内部可能觉得第一个字段就用范围,结果集会很大,回表效率不高,还不如全表扫描

为什么使用索引会比全表扫描要慢?

  • 因为索引扫描需要在索引树上进行查找,然后再通过索引指向的物理位置读取数据,这个过程可能涉及多次磁盘的IO操作,而全表扫描则是顺序的读取整个表的数据,磁盘的顺序读取的速度是很快的
  1. 强制走索引
EXPLAIN SELECT * FROM employees force index(idx_name_age_position) WHERE name > 'LiLei' AND age = 22 AND position ='manager';

image-20240103193950581

  • 虽然使用强制走索引让联合索引的第一个字段范围查找也走索引,扫描的行数rows看上去也少了点,但是最终查找效率不一定比全表扫描高,因为回表效率不高
  1. 覆盖索引优化
EXPLAIN SELECT name,age,position FROM employees WHERE name > 'LiLei' AND age = 22 AND position ='manager';

image-20240103194239718

  1. in和or在表数据量大的情况会走索引,在表记录不多的情况会选择全表扫描

image-20240103194359985

  1. like KK%一般情况下都会走索引

image-20240103194531851

  • 为什么会走索引
    • 用到了索引下推
    • 对于辅助的联合索引,正常情况下按照最前缀原则select * from employees where name like ‘LiLei%’ and age = 22 and position = 'manager’这种情况下只会走name索引,因为根据name字段过滤完,得到所有行里的age和position是无序的,无法很好使用索引
    • 在MYSQL5.6之前的版本,这个查询只能在联合索引匹配到名字是‘LiLei’开头的索引,然后拿这些索引对应的主键逐个回表,到主键索引上找出相应的记录,再对比age和position这两个字段的值是否符合
    • MySQL5.6引入了索引下推优化,可以在索引遍历的过程中,对索引中包含的所有字段先做判断,过滤掉不符合条件的记录之后再回表,可以有效减少回表次数。使用了索引下推优化后,上面那个查询在联合索引里匹配到名字是‘LiLei’开头的索引之后,同时还会在索引里过滤age和position这两个字段,拿这过滤完剩下的索引对应的主键id再会查整行数据
  • 范围查找没有使用索引下推优化
  • 估计是MySQL认为范围查找过滤的结果集过大。
Explain select * from employees where name > 'a'

image-20240103210937223

常见SQL优化

Order By 与Group By优化

  • Case 1

image-20240103215936412

  • Case 2

image-20240103220008601

  • Case 3

image-20240103220135744

  • Case 4

image-20240103220241440

  • Case 5

image-20240103220258271

  • Case 6

image-20240103220313181

  • Case 7

image-20240103220400738

  • Case 8

image-20240103220416551

优化总结

  • MySQL支持两种方式的排序filesort和index,Using index是指MySQL扫描索引本身完成排序。index效率高,filesort效率低
  • order by两种情况会使用using index
    • order by语句使用索引最左前列
    • 使用where子句与order by子句条件列组合满足索引最左前缀法则
  • 尽量在索引列上完成排序,遵循索引建立(索引创建的顺序)时的最走前缀法则
  • 如果order by的条件不在索引列上,就会产生file sort
  • 能用覆盖索引就用覆盖索引
  • group by与order by很类似,其实质是先排序后分组,遵循索引创建顺序的最左前缀法则,对应group by的优化如果不需要排序的可以加上order by null禁止排序,注意。where高于having,能写在where条件就不要使用having去限定了。

Using filesort文件排序原理详解

filesort文件排序方式

  • 单路排序:是一次性取出满足条件行的所有字段,然后在sort buffer中进行排序,用trace工具可以看到sort_mode信息里显示<sort_key, additional_fields>或者<sort_key, packed_additional_fields>
  • 双路排序(又叫回表排序模式):是首先根据条件取出相应的排序字段直接定位行记录的行id,然后在sort buffer中进行排序,排序完成之后需要再次取回其他需要的字段;用trace工具可以看到sort_mode信息里显示<sort_key, rowid>

MySQL通过比较系统变量max_length_for_sort_data(默认1024字节)的大小和需要查询的总字段大小来判断使用哪种排序模式

  • 如果字段的总长度小于max_length_for_sort_data,那么使用单路排序模式
  • 如果字段的总长度大于max_length_for_sort_data,那么使用双路排序模式.

单路排序的详细过程

image-20240104153100584

  • 从索引name找到第一个满足name条件的主键id
  • 根据主键id取出整行,取出所有字段的值,存入sort_buffer中
  • 从索引name找到下一个满足name条件的主键id
  • 重复上面第二、三步直到不满足name条件
  • 对sort_buffer中的数据字段position进行排序
  • 返回结果给客户端

双路排序的过程

  • 从索引name找到第一个满足name='zhuge’的主键id
  • 根据主键id取出整行,把排序字段position和主键id放入sort_buffer
  • 从索引name取下一个满足name="zhuge"记录的主键id
  • 重复三、四步直到不满足name=“zhuge”
  • 对sort_buffer中的字段和主键id按照字段position进行排序
  • 遍历号排序好的id和字段position,按照id的值回到原表中取出所有的字段的值返回给客户端。

索引设计原则

  • 代码先行,索引后上
  • 联合索引尽量覆盖条件
  • 不要在小基数字段上建立索引
  • 长字符串我们可以使用前缀索引(取前20个字符)
  • where与order by冲突时优先选择where
  • 基于慢sql做优化

索引设计实战

以社交场景APP来举例,我们一般会去搜索一些好友,这里面就涉及到对用户信息的筛选,这里肯定就是对用户user表搜索了,这个表一般来说数据量会比较大,我们先不考虑分库分表的情况,比如,我们一般会筛选地区(省市),性别,年龄,身高,爱好之类的,有的APP可能用户还有评分,比如用户的受欢迎程度评分,我们可能还会根据评分来排序等等。

对于后台程序来说除了过滤用户的各种条件,还需要分页之类的处理,可能会生成类似sql语句执行:

select xx from user where xx=xx and xx=xx order by xx limit xx,xx

对于这种情况如何合理设计索引了,比如用户可能经常会根据省市优先筛选同城的用户,还有根据性别去筛选,那我们是否应该设计一个联合索引 (province,city,sex) 了?这些字段好像基数都不大,其实是应该的,因为这些字段查询太频繁了。

假设又有用户根据年龄范围去筛选了,比如 where province=xx and city=xx and age>=xx and age<=xx,我们尝试着把age字段加入联合索引 (province,city,sex,age),注意,一般这种范围查找的条件都要放在最后,之前讲过联合索引范围之后条件的是不能用索引的,但是对于当前这种情况依然用不到age这个索引字段,因为用户没有筛选sex字段,那怎么优化了?其实我们可以这么来优化下sql的写法:where province=xx and city=xx and sex in (‘female’,‘male’) and age>=xx and age<=xx

对于爱好之类的字段也可以类似sex字段处理,所以可以把爱好字段也加入索引 (province,city,sex,hobby,age)

假设可能还有一个筛选条件,比如要筛选最近一周登录过的用户,一般大家肯定希望跟活跃用户交友了,这样能尽快收到反馈,对应后台sql可能是这样:

where province=xx and city=xx and sex in (‘female’,‘male’) and age>=xx and age<=xx and latest_login_time>= xx

那我们是否能把 latest_login_time 字段也加入索引了?比如 (province,city,sex,hobby,age,latest_login_time) ,显然是不行的,那怎么来优化这种情况了?其实我们可以试着再设计一个字段is_login_in_latest_7_days,用户如果一周内有登录值就为1,否则为0,那么我们就可以把索引设计成 (province,city,sex,hobby,is_login_in_latest_7_days,age) 来满足上面那种场景了!

一般来说,通过这么一个多字段的索引是能够过滤掉绝大部分数据的,就保留小部分数据下来基于磁盘文件进行order by语句的排序,最后基于limit进行分页,那么一般性能还是比较高的。

不过有时可能用户会这么来查询,就查下受欢迎度较高的女性,比如sql:where sex = ‘female’ order by score limit xx,xx,那么上面那个索引是很难用上的,不能把太多的字段以及太多的值都用 in 语句拼接到sql里的,那怎么办了?其实我们可以再设计一个辅助的联合索引,比如 (sex,score),这样就能满足查询要求了。

以上就是给大家讲的一些索引设计的思路了,核心思想就是,尽量利用一两个复杂的多字段联合索引,抗下你80%以上的查询,然后用一两个辅助索引尽量抗下剩余的一些非典型查询,保证这种大数据量表的查询尽可能多的都能充分利用索引,这样就能保证你的查询速度和性能了!

img

  • 9
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值