表初始化语句如下:
代码语言:javascript复制CREATE TABLE `t_student` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL DEFAULT '',
`class` varchar(50) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
index class_idx(class)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
insert into t_student(`name`,class) values('小明', '100'),('小詹', '200'),('小龙', '300'),('小红', '400'),('小哈', '500'),('小屁孩', '600');
执行查询的SQL语句如下:
代码语言:javascript复制select * from t_student where class between 200 and 300;
我们一起来看看这条 SQL 查询语句的执行流程:
- 在 class_idx 索引树上找到 class=200 的记录,取得 ID = 2;
- 再回到 ID 索引树查到 ID=2 对应的 行的结果数据R2;
- 在 class_idx 索引树取下一个值 class=300,取得 ID=3;
- 再回到 ID 索引树查到 ID=3 对应的 行结果数据R3;
- 在 class_idx 索引树取下一个值 class=400,不满足条件,循环结束。
在这个过程中,回到主键索引树搜索的过程,我们称为回表。可以看到,这个查询过程读了 class_idx 索引树的 3 条记录(步骤 1、3 和 5),回表了两次(步骤 2 和 4)。
因为主键索引的叶子节点关联的数据是整行数据,所以想要读取整行数据不得不回表。那么,什么情况下可以经过索引优化,避免回表过程呢?
覆盖索引
如果执行的语句是 select id from t_student where class between 200 and 300,这时只需要查 ID 的值,而 ID 的值已经在 class_idx 索引树上(普通索引的叶子节点数据是主键)了,因此可以直接提供查询结果,不需要回表。也就是说,在这个查询里面,索引 class_idx 已经“覆盖了”我们的查询需求,我们称为覆盖索引。 由于覆盖索引可以减少树的搜索次数,显著提升查询性能,所以使用覆盖索引是一个常用的性能优化手段。 需要注意的是,在引擎内部使用覆盖索引在索引 class_idx 上其实读了三个记录,步骤1、3、5,但是对于 MySQL 的 Server 层来说,它就是找引擎拿到了两条记录,因此 MySQL 认为扫描行数是 2。