上回我们主要研究了为什么使用索引,以及索引的数据结构。今天带你了解如何设计高性能的索引。
其中,有这么一个点,说的是 InnoDB 引擎中使用的是聚簇索引,其主索引的实现树中的叶子结点存储的是完整的数据记录,而辅助索引中存储的则只是辅助键和主键的值。
这样在用辅助索引进行查询时,会先查出主键的值,然后再去主索引中根据主键的值查询目标值。
比如,假想一个表如下图存储了 4 行数据。其中 Id 作为主索引,Name 作为辅助索引。
Id Name Company5 Gates Microsoft
7 Bezos Amazon
11 Jobs Apple
14 Ellison Oracle
对于聚簇索引,若使用主键索引进行查询,select * from tab where id = 14 这样的条件查找主键,则按照 B+ 树的检索算法即可查找到对应的叶节点,之后获得行数据。
若使用辅助索引进行查询,对 Name 列进行条件搜索,则需要两个步骤:
1、第一步在辅助索引 B+ 树中检索 Name,到达其叶子节点获取对应的主键值。
2、第二步根据主键值在主索引 B+ 树中再执行一次 B+ 树检索操作,最终到达叶子节点即可获取整行数据。
上面这个过程称为回表。
回表:在数据中,当查询数据的时候,在索引中查找索引后,获得该行的 rowid,根据 rowid 再查询表中数据,就是回表。
显然,使用辅助索引出现了回表操作,这势必会影响查询性能,那有什么办法能够减少回表吗?
下面就开始我们的主题:如何让 MySQL 索引更高效!
覆盖索引上面,我们查询的是 select *,如果是根据 Name 查询 Id 呢?即 select Id from tab where。
很明显,由于辅助索引 Name 上已经存储了 Id 的值,所以这时,查询便不会再次回表查询。
如果索引已经包含了所有满足查询需要的数据,这时我们称之为覆盖索引(Covering Index),这时就不再需要回表操作。
覆盖索引是一种非常强大的工具,能大大提高查询性能,只需要读取索引而不用读取数据有以下一些优点:
1、索引条目通常远小于数据行大小,只需要读取索引,则 MySQL 会极大地减少数据访问量。
2、因为索引是按照列值顺序存储的,所以对于 IO 密集的范围查找会比随机从磁盘读取每一行数据的 IO 少很多。
3、覆盖索引对 InnoDB 表特别有用。因为 InnoDB 的辅助索引在叶子节点中保存了行的主键值,所以如果二级主键能够覆盖查询,则可以避免对主键索引的二次查询;
由于覆盖索引可以减少树的搜索次数,显著提升查询性能,所以使用覆盖索引是一个常用的性能优化手段。
联合索引/最左匹配原则又名复合索引,由两个或多个列的索引。
它规定了 MySQL 从左到右地使用索引字段,对字段的顺序有一定要求。
另外,一个查询可以只使用索引中的一部分,更准确地说是最左侧部分(最左优先),这就是传说中的最左匹配原则。
即最左优先,如:
如果有一个 2 列的索引 (col1,col2),则相当于已经对 (col1)、(col1,col2) 上建立了索引;
如果有一个 3 列索引 (col1,col2,col3),则相当于已经对 (col1)、(col1,col2)、(col1,col2,col3) 上建立了索引;
但是 (col2,col3) 上并没有。
假定数据表有一个包含 2 列的联合索引(a, b),则索引的 B+ 树结构可能如下:
键值都是排序的,通过叶子节点可以逻辑上顺序的读出所有数据。
数据(1,1)(1,2)(2,1)(2,4)(3,1)(3,2)是按照(a,b)先比较 a 再比较 b 的顺序排列。
所以从全局看,a 是全局有序的,而 b 则不是。
基于上面的结构,对于以下查询显然是可以使用(a,b)这个联合索引的:
select * from table where a=xxx and b=xxx ; select * from table where a=xxx;但是对于下面的 sql 是不能使用这个联合索引的,因为叶子节点的 b 值,1,2,1,4,1,2 显然不是排序的。
select * from table where b=xxx只要满足最左前缀,就可以利用索引来加速检索。这个最左前缀可以是联合索引的最左 N 个字段,也可以是字符串索引的最左 M 个字符。
注意1、主键字段其实跟所有非主键索引建立了联合索引,只是说如果主键字段没有在联合索引中明确声明,只会在其他索引中处于最右边;
2、最左前缀匹配原则,MySQL 会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配。