MySQL索引扩展

问题

最近在看《MySQL实战45讲》中遇到一个比较困惑的问题。问题是:

有如下表

CREATE TABLE `geek` ( 
  `a` int(11) NOT NULL, 
  `b` int(11) NOT NULL, 
  `c` int(11) NOT NULL, 
  `d` int(11) NOT NULL, 
  PRIMARY KEY (`a`,`b`), 
  KEY `c` (`c`), 
  KEY `ca` (`c`,`a`), 
  KEY `cb` (`c`,`b`) 
) ENGINE=InnoDB;

有两条SQL如下:

select * from geek where c=N order by a limit 1; 
select * from geek where c=N order by b limit 1;

问题:哪个索引是多余的?

作者认为索引c与联合索引(c,a,b)效果相同。

看到这块就很不理解,为什么走索引c的时候会用到主键联合索引a,b?我们知道二级索引叶子节点存储的是主键id的值,在查询的时候,会先根据二级索引找到主键id的值,再进行回表去主键id的聚簇索引树中拿到具体的行数据。举个例子,k为普通索引,Id为主键索引。对于SQL select * from T where k = 5,执行过程是先去k索引树找到k=5对于的id值:500;再到ID索引树找到id =500对应的行数据,并返回;接着再去k索引树取下条数据k=6,不符合则返回。


(图片来源:《MySQL实战45讲》)

索引扩展

MySQL官网中介绍了索引扩展的原理:

InnoDB automatically extends each secondary index by appending the primary key columns to it.

InnoDB通过向每个二级索引添加主键列来自动扩展它。

举个例子, t1表有主键索引(i1,i2) 和辅助索引 k_d,但是InnoDB在内部扩展了这个索引k_d,会把主键值追加到索引列后,扩展后的索引变为 (d, i1, i2)。MySQL优化器通过使用扩展的辅助索引来进行更有效率地 连接、排序、ref、range查询。

optimizer_switch系统变量的use_index_extensions标志允许控制优化器在确定如何使用InnoDB表的辅助索引时是否考虑主键列。默认情况下,启用“使用索引”扩展。如果禁用使用系统扩展,可以通过以下指令:

SET optimizer_switch = 'use_index_extensions=off';

下面通过实验来验证这个结论,注意,本次实验基于数据库版本5.7。

(1)准备工作,建表和插入数据

CREATE TABLE t1 (
  i1 INT NOT NULL DEFAULT 0,
  i2 INT NOT NULL DEFAULT 0,
  d DATE DEFAULT NULL,
  PRIMARY KEY (i1, i2),
  INDEX k_d (d)
) ENGINE = InnoDB;

插入数据

INSERT INTO t1 VALUES
(1, 1, '1998-01-01'), (1, 2, '1999-01-01'),
(1, 3, '2000-01-01'), (1, 4, '2001-01-01'),
(1, 5, '2002-01-01'), (2, 1, '1998-01-01'),
(2, 2, '1999-01-01'), (2, 3, '2000-01-01'),
(2, 4, '2001-01-01'), (2, 5, '2002-01-01'),
(3, 1, '1998-01-01'), (3, 2, '1999-01-01'),
(3, 3, '2000-01-01'), (3, 4, '2001-01-01'),
(3, 5, '2002-01-01'), (4, 1, '1998-01-01'),
(4, 2, '1999-01-01'), (4, 3, '2000-01-01'),
(4, 4, '2001-01-01'), (4, 5, '2002-01-01'),
(5, 1, '1998-01-01'), (5, 2, '1999-01-01'),
(5, 3, '2000-01-01'), (5, 4, '2001-01-01'),
(5, 5, '2002-01-01');

(2)关闭索引扩展

查看优化器配置开关:show variables like '%optimizer_switch%';

可以看到MySQL默认是打开索引扩展功能的 use_index_extensions=on:

index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,engine_condition_pushdown=on,index_condition_pushdown=on,mrr=on,mrr_cost_based=on,block_nested_loop=on,batched_key_access=off,materialization=on,semijoin=on,loosescan=on,firstmatch=on,duplicateweedout=on,subquery_materialization_cost_based=on,use_index_extensions=on,condition_fanout_filter=on,derived_merge=on

执行关闭索引扩展的命令:SET optimizer_switch = 'use_index_extensions=off';

执行explain指令查看:EXPLAIN SELECT COUNT(*) FROM t1 WHERE i1 = 3 AND d = '2000-01-01'分析结果:

  • 使用了主键索引PRIMARY,key_len 长度为4,Extra 使用了Using where,说明使用主键i1查询到符合条件的结果集,然后再使用where 条件 d = ‘2000-01-01’ 进行过滤;

(3)开启索引扩展

执行开启索引扩展命令:SET optimizer_switch = 'use_index_extensions=on';

执行explain指令查看:EXPLAIN SELECT COUNT(*) FROM t1 WHERE i1 = 3 AND d = '2000-01-01'分析结果:

  • 实际使用的索引key是k_d,但是key_len不是4而是8,ref也是const 和const,同时Extra是Using index表面使用了覆盖索引。
  • 虽然我们以为优化器选择的索引key是k_d,但是实际使用的是索引扩展(d,i),这样就能解释为什么key_len=8=4+4, ref=const和const,Extra的覆盖索引是正好使用了(d,i)的覆盖索引

以上分析结果说明MySQL在执行的时候会使用扩展索引(d -> (d,i))来提高执行效率。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yUNCeoIe-1606999927485)(../../../Library/Application%20Support/typora-user-images/image-20201203110943578.png)]

注:5.7版本的MySQL文档的一个小漏洞

在上述第2步(2)关闭索引扩展的实验中,我们分析结果表面关闭索引扩展的情况下,explain分析得出的使用的key是主键key - PRIMARY,key_len = 4, extra = Using where.

但是官方5.7的文档结果却说使用的k_d单列索引,extra却走得是Using index覆盖索引,跟我分析的结果完全不一样。
在这里插入图片描述

经过多次实验发现MySQL 5.6版本下实验结果与文档中分析结果一致。因此,我猜测MySQL 5.7文档 https://dev.mysql.com/doc/refman/5.7/en/index-extensions.html 在关闭索引扩展的分析结果是直接抄了5.6的文档,没有做任何改动,上述的分析结果是错误的。

实际在MySQL底层实现时5.7可能已经有了变化,在count(*)时不会使用二级索引 + Using index的查询,而是直接选择主键PRIMARY的查询。


参考:https://dev.mysql.com/doc/refman/5.7/en/index-extensions.html

https://coderbee.net/index.php/db/20190106/1708

猜你喜欢

转载自blog.csdn.net/noaman_wgs/article/details/110568257