索引的代价

通过一个示例,来说明索引的代价。如下,给 person_info表中创建一个联合索引 idx_name_birthday_phone_number。

1 CREATE TABLE person_info(
2     id INT NOT NULL auto_increment,
3     name VARCHAR(100) NOT NULL,
4     birthday DATE NOT NULL,
5     phone_number CHAR(11) NOT NULL,
6     country varchar(100) NOT NULL,
7     PRIMARY KEY (id),
8     KEY idx_name_birthday_phone_number (name, birthday, phone_number)
9 );

以 idx_name_birthday_phone_number索引为例,看下边这个查询:

SELECT * FROM person_info WHERE name > 'Asa' AND name < 'Barlow';

在使用 idx_name_birthday_phone_number索引进行查询时大致可以分为这两个步骤:

【1】索引 idx_name_birthday_phone_number对应的 B+树中取出 name值在 Asa~Barlow之间的用户记录。
【2】由于索引 idx_name_birthday_phone_number对应的 B+树用户记录中只包含name、birthday、phone_number、id这4个字段,而查询列表是*,意味着要查询表中所有字段,也就是还要包括 country字段。这时需要把从上一步中获取到的每一条记录的 id字段,再去聚簇索引对应的 B+树中找到完整的用户记录,也就是我们通常所说的回表,然后把完整的用户记录返回给查询用户。

读取索引 idx_name_birthday_phone_number数据中,在Asa~Barlow之间的记录在磁盘中的存储是相连的,集中分布在一个或几个数据页中,我们可以很快的把这些连着的记录从磁盘中读出来,这种读取方式我们也可以称为顺序I/O

获取到的记录的 id字段的值可能并不相连,而在聚簇索引中记录是根据id(也就是主键)的顺序排列的,所以根据这些并不连续的 id值到聚簇索引中访问完整的用户记录可能分布在不同的数据页中,这样读取完整的用户记录可能要访问更多的数据页,这种读取方式我们也可以称为随机I/O

所以这个使用索引 idx_name_birthday_phone_number的查询有这么两个特点:
【1】使用到两个 B+树索引,一个二级索引,一个聚簇索引
【2】访问二级索引使用顺序I/O,访问聚簇索引使用随机I/O

需要回表的记录越多,使用二级索引的性能就越低,甚至让某些查询宁愿使用全表扫描也不使用二级索引。比方说 name值在Asa~Barlow之间的用户记录数量占全部记录数量90%以上,那么如果使用 idx_name_birthday_phone_number索引的话,有90%多的 id值需要回表,这不是吃力不讨好么,还不如直接去扫描聚簇索引(也就是全表扫描)。

查询优化器做的工作,查询优化器会事先对表中的记录计算一些统计数据,然后再利用这些统计数据根据查询的条件来计算一下需要回表的记录数,需要回表的记录数越多,就越倾向于使用全表扫描,反之倾向于使用二级索引 + 回表的方式。比方说上边的查询可以改写成这样:

SELECT * FROM person_info WHERE name > 'Asa' AND name < 'Barlow' LIMIT 10;

添加了 LIMIT 10的查询更容易让优化器采用二级索引 + 回表的方式进行查询。

posted @ 2020-11-14 13:48  Java程序员进阶  阅读(156)  评论(0编辑  收藏  举报