MySQL学习-4|深入浅出索引(下)

前文回顾

环境: MySQL 5.7.24, for linux-glibc2.12 (x86_64)

索引是为了 提高数据查询 的效率,MySQL 中,索引是在存储引擎层1实现的。

示例分析

假设,表T有一个主键ID,表中有字段k,并且在k上有索引。新增5条R1~R5记录,如下

-- CREATE
mysql> create table T(
       id int primary key, 
       k int not null, 
       name varchar(16),
       index k(k)) engine=InnoDB;
-- INSERT
mysql> insert T(`id`, `k`, `name`) values
(100,1,'nm1'),
(200,2,'nm2'),
(300,3,'nm3'),
(500,5,'nm5'),
(600,6,'nm6');

表T的主键ID和k索引的两棵树示例如下:
InnoDB索引组织结构
如果执行以下SQL语句,需要执行几次树的搜索操作,会扫描多少行?

mysql> select * from T where k between 3 and 5;

这条SQL查询语句的执行流程简化如下:

步骤执行动作
1k索引树上查找k=3的记录,取得ID=300
2回到ID索引树查到ID=300对应的记录R3
3k索引树取下一个值k=5的记录,取得ID=500
4再回到ID索引树查到ID=500对应的记录R4
5k索引树取下一个值k=6的记录,不满足where条件,循环结束

以上的查询步骤中,读了k索引树的3条记录(步骤1、3、5),回表了2次(步骤2、4)。

回到主键索引树搜索的过程,成为回表

索引设计

覆盖索引

覆盖索引 可以减少树的搜索次数,显著提升查询性能。所以使用覆盖索引是一个常用的性能优化手段。

如果执行以下SQL语句select ID,因为只需查ID的值,而ID的值已经在k索引树上,因此可以直接提供查询结构,不需要回表。也就是说,索引k已经覆盖了查询需求,称之为覆盖索引

mysql> select ID from T where k between 3 and 5;
-- select * from T where k between 3 and 5;
  • 需要注意的是,在引擎内部使用覆盖索引在索引k上其实读了3个记录,R3~R5(对应的索引k上的记录项),但是对于 MySQLServer 层1来说,它就是找存储引擎拿到了2条记录,因此MySQL认为扫描行数是2。

最左前缀原则

B+树 这种索引结构,可以利用索引的最左前缀,来定位记录。

CREATE TABLE `tuser` (
  `id` int(11) NOT NULL,
  `id_card` varchar(32) DEFAULT NULL,
  `name` varchar(32) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  `ismale` tinyint(1) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `id_card` (`id_card`),
  KEY `name_age` (`name`,`age`)
) ENGINE=InnoDB

在实际业务中,如果为每一种查询都设计一个索引,索引就太多了;但如果查询在业务中出行的概率不高,但有这种业务,单独为一个不频繁的请求创建一个索引,有些浪费。这种情况可以利用索引的最左前缀规则。

为了直观说明这个概念,用name_age(name,age)这个联合索引来分析。
最左前缀

索引项是按照索引定义里出现的字段的顺序排序的。

当需求是查询所有姓氏是"张"的人,SQL语句条件是where name like '张%',这时,能够用上name_age(name,age)这个索引,查找到第一个符合条件的记录是ID3,然后向后遍历,直到不满足条件为止。

  • 可以看到,不只是索引的全部定义,只要满足最左前缀,就可以利用索引来加速检索。
  • 最左前缀,可以是联合索引的最左N个字段,也可以是字符串索引的最左M个字符

在建立联合索引的时候,如何安排索引内的字段顺序?评估的标准是,索引的复用能力
如果通过调整顺序,可以少维护一个索引,那么这个顺序往往就是需要优先考虑采用的。

如果既有联合查询,又有基于name,age各自的查询呢?例如,查询条件里只有age的需求,是无法使用name_age(name,age)这个索引的。这时,需要同时维护两个索引因为name字段是比age字段大的,所以建议的2个索引是name_age(name,age)联合索引、age(age)的单字段索引。

这时候需要考虑的原则就是空间了。

索引下推

不符合最左前缀规则的,会怎么样呢?
仍以上述表中的name_age(name,age)这个联合索引为例,如果现在有个需求,需要检索出表中"姓氏为张,且年龄是10岁的所有男孩"。那么SQL语句可以这样写:

mysql> select * from tuser where name like '张%' and age=10 and ismale=1;

这个SQL语句,根据最左前缀规则,所以在搜索name_age索引树的时候,只能用"张",找到第一个满足条件的记录ID3。当然,这也不错,总比全表扫描要好。
接下来,要判断其他条件是否满足。

MySQL 5.6之前,只能从ID3开始一个个回表,到主键索引上找出数据行,再对比字段值。
MySQL 5.6引入索引下推(index condition pushdown)优化,可以在索引遍历过程中,对索引中包含的字段先做判断,直接过滤掉不满足条件的记录,减少回表次数。

无索引下推的执行流程(每一个虚线箭头表示回表一次)
无索引下推

  • InnoDB并不会去看name_age(name,age)索引里age的值,只是按顺序把name的第一个字是"张"的记录一条条取出来回表。因此,需要回表4次。

有索引下推的执行流程(每一个虚线箭头表示回表一次)
索引下推

  • InnoDB在name_age(name,age)索引内部就判断了age的值是否等于10,对于不等于10的记录,直接判断并跳过。只需要对等于10的ID4ID5取出来回表。因此,需要回表2次。

总结

覆盖索引、前缀索引、索引下推的概念。在满足需求的情况下,尽量少的访问资源是数据库设计的重要原则之一。
在使用数据库时,尤其在设计表结构时,也要以减少资源消耗作为目标。

  • 不论是删除主键,还是创建主键,都会将整个表重建。
-- 为什么表数据删掉一半,表文件大小不变?
alter table T engine=InnoDB;

参考资料

《高性能MySQL》
《MySQL实战45讲》 作者:丁奇

写在后面

之前学习了大神丁奇的《MySQL实战45讲》,目前在看《高性能高MySQL》,也想自己整理一下MySQL知识点,发现力不从心,也发现大神之所以是大神,那是因为真的牛。

推荐大家还是去学习丁奇的《MySQL实战45讲》,条理清晰,循序渐进,深入浅出,通俗易懂。而且每一讲后面都有高质量的留言评论, 从中能获益良多。感谢!

  • 如有 错误之处 还请多多指正。希望能给您带来帮助。

  1. MySQL基础架构 ↩︎ ↩︎

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值