前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >面试官:谈谈你对mysql联合索引的认识?

面试官:谈谈你对mysql联合索引的认识?

作者头像
程序员小饭
发布2021-03-04 14:18:38
1.5K1
发布2021-03-04 14:18:38
举报
文章被收录于专栏:golang+phpgolang+php

前言

只要说到联合索引,大家肯定都会想到“最左匹配”,相信不用解释大家也知道是啥意思,也很简单,但是联合索引中又有不少特殊情况,

比如:

代码语言:javascript
复制
SELECT * FROM table WHERE a = 1 and b = 2 and c = 3;

这个如何建立索引?如果回答是直接建立索引(a,b,c)的话,那么可以直接回家等通知了,其实对于这个例子,建立索引(a,b,c),(a,c,b),(b,a,c),(b,c,a),(c,b,a),(c,a,b)这几种都是可以的,之所以能这样是因为mysql在执行的过程中有会经历优化器这一层,在这层会自动优化顺序。

再比如,在一张表中(表名test),有联合索引(a,b,c), 那么

代码语言:javascript
复制
select a from test order by a,b,c;

是走索引的,但是

代码语言:javascript
复制
select * from test order by a,b,c;

是不走索引的,这又是为什么呢?

所以由此看来联合索引并不是简简单单的"最左匹配"几个字能完全涵盖的,所以咱们今天从原理来理解mysql的联合索引,从而达到真正的懂和理解联合索引的目的。

最左匹配及其原理

最左匹配

最左匹配原则就是指在联合索引中,如果你的 sql 语句中用到了联合索引中的最左边的索引,那么这条 sql 语句就可以利用这个联合索引去进行匹配。例如某表现有索引(a,b,c),现在你有如下语句:

代码语言:javascript
复制
select * from t where a=1 and b=1 and c =1;     #这样可以利用到定义的索引(a,b,c)

select * from t where a=1 and b=1;     #这样可以利用到定义的索引(a,b,c)

select * from t where a=1;     #这样也可以利用到定义的索引(a,b,c)

select * from t where b=1 and c=1;     #这样不可以利用到定义的索引(a,b,c)

select * from t where a=1 and c=1;     #这样不可以利用到定义的索引(a,b,c)

也就是说通过最左匹配原则你可以定义一个联合索引,但是使得多种查询条件都可以用到该索引。值得注意的是,当遇到范围查询(>、<、between、like)就会停止匹配。也就是:

代码语言:javascript
复制
select * from t where a=1 and b>1 and c =1;     #这样a,b可以用到(a,b,c),c不可以

这条语句只有 a,b 会用到索引,c 都不能用到索引。这个原因可以从联合索引的结构来解释。

原理

我们先来看看联合索引在b+树中是什么样的。比如我们在一张表中创建了索引(a,b),那么在b+树中,它的表现形式是这样的

字段名

a

b

1

1

1

2

2

1

2

4

3

1

3

2

咱们来先观察一下,只看a字段,a字段的值分别是 1,1,2,2,3,3.我们会发现 a字段其实是排序好的,而b字段的顺序是 1,2,1,4,1,2,却是乱序的,但是在字段a相等的情况下,字段b是经过排序的。也就是说b是一种全局无序,局部相对有序状态!

所以为什么直接用b=2,是无法走索引的,但是在a=1 and b=2的情况下是可以走到索引的,而你执行a > 1 and b = 2时,a字段能用到索引,b字段用不到索引。因为a的值此时是一个范围,不是固定的,在这个范围内b值不是有序的,因此b字段用不上索引。综上所示,最左匹配原则,在遇到范围查询的时候,就会停止匹配。

实战

咱们多看一些常见的实战题,多思考,这些题都会了,联合索引应该能够做到举一反三了。

question:1

如何给下列sql语句加上联合索引?

代码语言:javascript
复制
select * from test where a = 1 and b = 1 and c = 1;

建议思考一下再看答案。。。。。。。

answer

咱们一看,直接加索引(a,b,c)就可以了,其实不然,也不能说这个答案不对,只能说这个答案不够完整。因为mysql在执行的时候会经历优化器过程,所以会把sql语句自动优化成符合索引的顺序,所以索引(a,b,c) (a,c,b) 或者是(c,b,a)都是可以的,那我们究竟如何确定索引呢?这个就得根据实际情况来了,比如a字段是表示性别的,只有0,1和2三个值来表示 未知,男,女三种性别,那么把a字段放在联合索引的最后会比较合适,反正哪个字段的内容重复率越低,就把哪个字段往联合索引的后面放。

question:2

如何给下列sql语句加上索引

代码语言:javascript
复制
SELECT * FROM table WHERE a > 1 and b = 2; 

建议思考一下再看答案。。。。。。。

answer

如果咱们建立索引(a,b),那么a>1是可以走到索引的,但是b=2就没法走到索引了。但是如果咱们建立索引(b,a),那么sql语句会被自动优化成 where b=2 and a> 1,这样a和b都能走到索引,所以建立索引(b,a)比较合适

question:3
代码语言:javascript
复制
SELECT * FROM `table` WHERE a > 1 and b = 2 and c > 3; 
answer

其实这个sql语句加上索引的方式和上面question2差不多,加上索引(b,a)或者(b,c)都是可以的。

question:4

这个情况比较多,咱们直接看sql语句分析吧

代码语言:javascript
复制
SELECT * FROM `table` WHERE a = 1 ORDER BY b;

对于上述情况,其实加上一个联合索引(a,b)就行,因为在a=1的情况下,b的顺序都是排好的,避免了再次排序

代码语言:javascript
复制
SELECT * FROM `table` WHERE a > 1 ORDER BY b; 

这种情况下如果建立了联合索引(a,b),那么在a>1的情况下b是无序的,需要对b再次排序,所以直接在字段a上建立索引就可以了,完全没必要用联合索引。

代码语言:javascript
复制
SELECT * FROM `table` WHERE a = 1 AND b = 2 AND c > 3 ORDER BY c;

上述sql语句直接建立索引(a,b,c)就可以了,具体原因不再赘述。

下面咱们看几种比较麻烦的情况

question:5

咱们有表 test ,id为主键,字段(a,b,c)为联合索引,另外还有一个字段d和e,除了e为varchar,其余全是int,表的sql语句如下

代码语言:javascript
复制
CREATE TABLE `test` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `a` int(11) unsigned NOT NULL DEFAULT '0',
  `b` int(11) unsigned NOT NULL DEFAULT '0',
  `c` int(11) unsigned NOT NULL DEFAULT '0',
  `d` int(11) unsigned NOT NULL DEFAULT '0',
  `e` varchar(10) NOT NULL DEFAULT '',
  PRIMARY KEY (`id`),
  KEY `idx_a_b_c` (`a`,`b`,`c`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
情况1

咱们执行sql语句

代码语言:javascript
复制
select * from test where a>1;

结果却是不走索引的(用explain查看type为ALL),这是为什么呢?咱们查询条件是a>1,的确是符合最左匹配原则的,那为什么没有走索引呢?其实这个也和咱们mysql的自动优化有关,咱们先来看一看 走索引的过程。首先会把a>1的情况都根据索引筛选出来,因为a是unsigned的,也就是是大于0的,所以a>0的情况基本上是把所有数据筛选出来了,其实这个还不是问题的关键所在,因为咱们查询的字段是select * ,所以每一次查询出来结果之后还需要回表,回表次数是非常多的,所以mysql在内部直接自动优化了,让它不走索引,直接一次性全部查出来算了。

情况2

咱们执行sql语句

代码语言:javascript
复制
select id from test where a>1;

这次是走索引的了,原理其实和上面情况一样,但是因为咱们查询的字段是id,不需要回表,同样,查询字段 id,a,b,c这四个字段都是不需要回表的

情况3

test表中一共1000000条记录 咱们执行sql语句

代码语言:javascript
复制
select * from test where a>999999;

咱们用explain查看这次却是走索引的了,为什么呢?因为虽然需要回表,但是咱们的限制条件a比较大,所以相对的回表次数也少了很多,所以这次走索引比较划算,性能会比较好。

question:6
代码语言:javascript
复制
sql1:  select * from test order by a,b,c;
sql2:  select a from test order by a,b,c;

同样还是question5的那张表,请问这俩sql哪个会走索引?答案是sql2会走索引,sql1不会,因为联合索引a,b,c的确是按照a,b,c的顺序排好的,但是如果只是select a ,那直接找到叶子节点就可以返回,如果是select * 就还是需要不断的回表,所以mysql会直接选择不走索引直接查询出来,然后用内存进行排序。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-02-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员养成日记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 最左匹配及其原理
    • 最左匹配
      • 原理
      • 实战
      相关产品与服务
      云数据库 SQL Server
      腾讯云数据库 SQL Server (TencentDB for SQL Server)是业界最常用的商用数据库之一,对基于 Windows 架构的应用程序具有完美的支持。TencentDB for SQL Server 拥有微软正版授权,可持续为用户提供最新的功能,避免未授权使用软件的风险。具有即开即用、稳定可靠、安全运行、弹性扩缩等特点。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档