当前位置: 首页 > 后端技术 > Java

在查询中,是否可以同时使用多个索引?

时间:2023-04-01 21:20:54 Java

其实我们之前讲的表返回就是同时使用了两棵索引树。首先在二级索引树中查找对应的主键值,然后在主键索引树中查询完整的记录。但是我今天的问题是,两个不同的二级索引树会同时生效吗?理论上应该是可以同时生效的,不然这个MySQL也太傻了。不过根据宋歌日常的开发经验,这种事情还是尽量避免吧。如果同时搜索两个索引树,很可能是你的索引设计有问题。这时候就需要检查索引设计是否合理。加粗的是实践经验,但是两个指标同时生效的知识点还是需要了解的,一起来看看吧。1.索引合并比如我有如下表结构:CREATETABLE`user`(`id`int(11)unsignedNOTNULLAUTO_INCREMENT,`username`varchar(32)COLLATEutf8mb4_unicode_ciDEFAULTNULL,`address`varchar(32)整理utf8mb4_unicode_ciDEFAULTNULL,`password`varchar(32)整理utf8mb4_unicode_ciDEFAULTNULL,`email`varchar(16)整理utf8mb4_unicode_ciDEFAULTNULL,PRIMARYKEY(`id`),KEY`usernameadd`(`username`s),KEY`(`address`))ENGINE=InnoDBAUTO_INCREMENT=100001DEFAULTCHARSET=utf8mb4COLLATE=utf8mb4_unicode_ci;这张表中有用户名和地址两个索引,注意是两个索引,每个索引都有一个字段,这个不是联合索引。现在我的查询SQL如下:select*fromuserwhereusername='1'oraddress='1';有两个搜索条件,用户名和地址,这是两个索引,属于两个不同的索引树。那么它在搜索的时候会不会同时搜索两个索引树呢?或者只搜索一个索引树,然后用另一个搜索条件过滤第一次树搜索的结果?我们看一下数据库执行计划:快速浏览一下执行计划,你可以猜到这里实际上使用了两个索引,并且这个执行计划中有几个新面孔:类型是index_merge。额外的是使用union(username,address);用在什么地方。这种类型的index_merge是索引合并。2.老版本的方法当然这个index_merge一开始是没有的,是从MySQL5.0引入的。虽然大家基本上不会再使用MySQL5.0之前的版本了,但是在这里说一下,以加深大家对MySQL的理解。在MySQL5.0之前,对于上面我们给出的查询SQL,是不会使用索引的,会扫描全表。那时候,如果你想实现上面的查询,但是又想使用索引,你的SQL就得这样写:select*fromuserwhereusername='1'unionallselect*fromuserwhereaddress='1'和用户名!='1'但是这种写法显然有点笨拙。因此,从MySQL5.0开始,查询中可以自动使用多个索引对结果进行扫描合并,也就是我们所说的索引合并(index_merge)。3.三种情况下的索引合并这个算法有三种变体,我们分别来看。3.1union这是两个索引的并集。让我们看看下面的SQL:select*fromuserwhereusernamelike'1%'oraddresslike'1%';这条SQL在执行过程中会涉及到两个索引,需要在两个索引树中查找。我们看一下SQL的执行计划,找到搜索结果的并集:我们可以看到在这个执行计划中已经发生了索引合并(见type,key,extra)。那么只要有两个索引查询,索引合并就一直发送?再看一个栗子:select*fromuserwhereusername>'a'oraddress='1';我们看一下,只是搜索条件变了一点,这里没有索引合并,而是全表扫描,这是为什么呢?这就引出了一个索引合并的条件,即每个索引对应的查找条件,查找到的主键必须是有序的。如果找到的主键乱序,抱歉,不能使用索引合并。在二级索引中,数据按照二级索引的顺序进行排序。结构类似如下:usernameprimarykeya20b30c9c10c18d1d5当username相同时,主键顺序。当用户名不同时,无法保证主键的顺序。如果获取的主键乱序,则无法实现索引合并。这就引出了另外一个问题,为什么只有获取到的主键是有序的,才能进行索引合并呢?因为只有主键有序,以后去重(union,sort-union)或者求交(intersect)的时候效率会更高。从MySQL5.0开始,索引合并默认开启。当然你也可以选择关闭。关闭联合索引合并的方法如下:SEToptimizer_switch='index_merge_union=off';关闭后,看执行计划:可以看到,还是发生了Indexmerging,不过这次不是union,而是sort_union,接下来看看sort_union是什么。3.2sort_unionsort_union与union基本相同,但多了一个排序能力。因为我们前面说过,如果拿到无序主键,就不会发生索引合并,最终可能会直接进行全表扫描。所以MySQL中又实现了一个sort_union,就是同时在username索引树和address索引树中查找,分别对主键值进行排序,排序后进行去重,然后回表进行得到完整的数据。与union相比,主要是加粗这一步。那我们继续,关闭sort_union,如下:SEToptimizer_switch='index_merge_sort_union=off';关闭后,进入执行计划,如下:此时没有进行索引合并,直接进行全表扫描。3.3intersect这是求两个索引的交集。例如,下面的SQL:select*fromuserwhereusernamelike'1%'andaddresslike'1%';此SQL在执行过程中可能有交集。当然这也不是绝对的,要看优化器的优化情况。宋大哥试了好久,就是无法复现一个例子,主要是我的模拟数据不太对。如果你有现成的使用intersect的例子,欢迎留言分享(使用intersect会出现在执行计划Extra中)。但我将在这里与您分享这个原则。我们看下图:假设网上有二级索引S和TSearch,S上的搜索条件是usernamelike'1%',T上的搜索条件是addresslike'1%',那么在查找的过程,各自得到的主键id是有序的,也是相交的前提):先到二级索引S中查找,找到符合条件的第一条记录。由于二级索引的叶子节点存放的是主键值,此时拿到主键值后,先不要急着回表。接下来到二级索引T中查找,找到符合条件的第一条记录,得到对应的主键值。比较第一步和第二步查找得到的主键值:3.1如果主键值不相等,舍弃值小的主键,留下大的主键,取这个下次在S上搜索时,将较大的主键与在S上搜索到的主键进行比较。3.2如果主键值相等,则说明主键满足搜索条件,则将这个主键带回桌子。重复前三个步骤,直到各个索引中没有满足条件的记录。这称为交叉获取主键。好了,索引合并的三种情况就到这里了。4.总结很多朋友可能会说,既然有了索引合并,那我是不是可以随便创建一个索引呢?不不不!索引合并是最后的手段。如果出现索引合并,很可能是你设计的索引不合理,所以要考虑如何优化索引。参考:https://dev.mysql.com/doc/ref...《MySQL 是怎么运行的》《高性能 MySQL》https://www.modb.pro/db/29619