MySQL索引作为数据库优化的常用手段之一在项目优化中经常会被用到, 但是如何建立高效索引,有效的使用索引以及索引优化的背后到底是什么原理?这次我们深入数据库索引,从索引的数据结构开始说起.

索引原理

索引为什么能提高查询效率?当我们有一个索引index(a)之后,写一个查询语句where a = 4.索引是怎么工作的.在学数据结构的时候学过红黑树,这是现如今使用的最广泛了的数据结构之一了,原因就在于它查询高效.

上图就是一颗红黑树.它有一个基本特性 :

某个节点的左子树的值必然都小于当前节点的值

某个节点的右子树的值必然都大于当前节点的值

所以当我们想要找值为6的节点的时候,即类似:where node=6.它从节点13开始,

只要查询的值小于当前节点就顺着左子数查找

要查询的节点大于当前节点就顺着当前节点的右子树查找.

所以只要经过4次的查找(13,8,1,6)就可以找到6.由于红黑树的神奇特性,所以在查找的时候可以在O(log n)的时间内做查找操作.现在我们考虑一下,如果我们索引使用了红黑树之后,它怎么帮助我们提高查询效率.

首先,每个节点必然是一个结构体,包含如下属性:

node {

node *left, //指向其左子树的指针

node *right, //指向其右子树的指针

Point *p //指向表中某一行的指针.

int data // 当前节点的值

}

当我们对某个建立一个索引的时候,MySQL就会把这个字段的所有值建立起一个红黑树,红黑树中的每个节点会有一个指针,这个指针指向当前数据的地址,比如A21是13所在行的指针,那么红黑树中值为13的节点就有一个指针指向这一行.

现在我们来模拟一遍数据库查找的流程:SQL语句为select * from table_name where a = 25,并且a字段建立了索引,那么查询的时候就不用遍历表了,只要查询红黑树,只要经过三次查找(13,17,25)就可以得到a=25所在行的指针.有了指针就可以读取到一整行的数据.

以上就是索引优化的原理.但是一般情况下,数据库中的数据是存放在磁盘中,读取磁盘中的数据要考虑到磁臂移动花费的时间给查询带来的影响,所以数据库中使用的索引一般不是红黑树,而是B树.虽然说采用的数据结构不一样,但是其原理都是一致的,所以即使是B树,我们仍然可以按照上面的方式来理解索引查询优化原理.

MyISAM && InnoDB

MyISAM和InnoDB是MySQL常用的两个存储引擎,两个也都是采用B树作为索引的数据结构,但是虽然如此,两者还是有很大区别的.

MyISAM索引中的指针就是指向数据所在地址

当有两个索引的时候,就分别有两个B树,两个索引的指针都是指向数据所在地址

MyISAM的这种索引方式被称为非聚集索引.而在InnoDB中,索引结构跟MyISAM有比较大的区别.

InnoDB中,整个数据表就是按照B树来存储着,整个表就是一颗巨大的B树.整个b树是按照表的主键索引的,所以一般InnoDB必须要求表有一个主键,如果没有的话,InnoDB会隐式的生成一个6个字节的整数型作为索引.

如果建立了其他索引的话,那么其索引的指针不是像MyISAM保存指向数据所在地址,而是保存主键值.这意味着InnoDB在使用非主键索引的时候需要遍历两次索引,一次遍历索引找到主键值,根据主键再次遍历主键索引找到数据行.

InnoDB中每个节点的数据类似:

node {

node *left //左子树指针

node *right //右子树指针

int index //索引值

Data data //index所在行的一整行数据

}

根据InnoDB索引的特殊性.所以可以得出一些使用InnoDB存储引擎的注意方式:

表最好要有一个主键.避免MySQL为我们隐式生成一个主键.

主键除了确保唯一性,而且占用的空间要少,因为非主键索引都是保存主键值的,如果主键为类似身份证这类数据,那么会导致索引过大.

主键最好有auto_increment,不然再每次插入非单调数据的时候为了保持B树的结构会频繁分裂树结构导致性能降低

联合索引

要想高效使用索引,就得知道什么样的查询语句会使用到索引.对于单列索引,只要where字句包含了索引就可以使用到索引.但是很多人很容易忽略联合索引,以为联合索引只是单纯的几个单列索引叠加在一起.其实不然,如果能够建立优秀的联合索引,效率会比建立多个单列索引好上很多.这里我们使用MySQL的Explain命令来分析SQL执行的过程,所以在深入索引之前来看看Explain怎么使用

explain

使用Explain只要在查询语句前面加上explain就可以了.

mysql> explain select * from t1 where a=1 and b=2 and c=3;

+----+-------------+-------+------+---------------+---------+---------+-------------------+------+-------+

| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |

+----+-------------+-------+------+---------------+---------+---------+-------------------+------+-------+

| 1 | SIMPLE | t1 | ref | idx_abc | idx_abc | 15 | const,const,const | 1 | NULL |

+----+-------------+-------+------+---------------+---------+---------+-------------------+------+-------+

1 row in set (0.00 sec)

如上 .执行Explain之后会返回给我们类似上面的数据.这里挑几个我们下面重点用到的字段.注意,有些没有提到的字段并不是不重要,只是我们重点在于使用这些字段分析索引.所以可以略过一些字段.

Extra

当值包含using index的时候说明使用了覆盖索引.什么是覆盖索引,即select要查询的字段正好就是当做索引的字段.比如上面当查询为select a,b,c from t1 where a=1 and b=2 and c=3.而我们正好有一个索引idx_abc(a,b,c).所以这个查询就使用到了覆盖索引.使用覆盖索引有什么好处?回忆一下上面索引的原理,查询的时候查询到了B树的某一个节点,要得到这个节点中保存的主键的值,然后再次遍历主键索引才能查询到数据.如果我们要查询的数据就是索引字段,那么就避免了第二次查找索引.

当值包含Using filesort说明MySQL在查询完要的数据之后,还要对数据进行一次排序才能返回结果.

key_len

表示当前查询使用到索引的字节数.通过这个字段我们可以分析出在查询的时候是否有效的使用了联合索引.那么如何计算ken_len?,这里有几个可以遵循的法则:

key_len 等于索引列类型字节长度,如int占据4个字节.

如果索引列为变长类型的数据需要 +2 字节,这个变长类型包括varchar,以及text等长数据建立的部分索引.

如果索引列允许为空,那么需要 +1 字节

字符类型的索引长度跟字符集有关.

根据上面的法则,如果有一个索引a,其字段定义为a varchar(10),并且a保存的是utf-8的数据,那么:

varchar为变长数据 需要 +2 字节

字段没有定义not null,说明允许为空, 需要 +1 字节

保存的是utf-8数据,一个字符占据3字节, 需要 10*3 字节

所以,这个索引的长度为:2 + 1 + 10*3=33字节

key&&possible_key

key字段表示当前搜索使用到的索引

possible_可以表示搜索之前MySQL估计可能使用到的索引

type

这个字段说明了MySQL是如何查找表中的行.这个字段有如下的可能值,这些值从上到下依次表明了本次查询从最差到最优.

ALL : 表示MySQL必须扫描整张表才能找到所需要的行.注意这里的扫描是指从表的第一行数据开始扫描.

index : 跟ALL的一样 需要扫描全表才能找到需要的行,但是不同的是扫描的顺序不是从表的第一行开始,而是根据索引的顺序扫描的.这个有一点好处是避免了排序,因为索引本身已经是有序的.

range : 这种查询跟index不同的地方在于不必进行全表扫描这种类型的查询一般在where字句中带有

ref : 这种查找一般在使用复合索引的时候会出现,具体含义在下文给出.

除了这些之外, 还有另外的eq_ref,const,system,null等可能.由于下文没有出现这些可能性,所以就不一一说明了.

好了,懂得根据Explain分析SQL执行过程之后,我们来看看如何使用复合索引才是最高效的.

CREATE TABLE `t1` (

`a` int(11) DEFAULT NULL,

`b` int(11) DEFAULT NULL,

`c` int(11) DEFAULT NULL,

KEY `idx_abc` (`a`,`b`,`c`)

) ENGINE=InnoDB DEFAULT CHARSET=utf-8

有上面的表结构,我们建立了一个复合索引idx_abc,其实相当于建立了三个索引:idx(a),idx(a,b),idx(a,b,c)三个索引.但是在具体查询的时候,是否能够用上这些索引还需要深入分析.

全匹配查询

mysql> explain select * from t1 where a=1 and b=2 and c=3;

+----+-------------+-------+------+---------------+---------+---------+-------------------+------+-------+

| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |

+----+-------------+-------+------+---------------+---------+---------+-------------------+------+-------+

| 1 | SIMPLE | t1 | ref | idx_abc | idx_abc | 15 | const,const,const | 1 | NULL |

+----+-------------+-------+------+---------------+---------+---------+-------------------+------+-------+

1 row in set (0.00 sec)

当查询精确匹配索引中的每一个字段的时候,就会使用到(a,b,c)索引.所以这里的key_len等于15,即 4+1+4+1+4+1=15.这里所谓的精确匹配搜索指的是查询类型为 = 或者 in 的时候.

而且这里要注意一点,MySQL对索引的顺序是敏感的,即定义索引的时候顺序为idx_abc(a,b,c),那么查询时候字句where的出现顺序也应该是a,b,c才可以用到索引.但是由于MySQL优化器会在查询之前帮我们调整顺序.所以,即使你查询写成select * from t1 where b=1 and a=2 and c=3;仍然可以使用到索引.

最左前缀

mysql> explain select * from t1 where a =2 and c=4;

+----+-------------+-------+------+---------------+---------+---------+-------+------+--------------------------+

| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |

+----+-------------+-------+------+---------------+---------+---------+-------+------+--------------------------+

| 1 | SIMPLE | t1 | ref | idx_abc | idx_abc | 5 | const | 1 | Using where; Using index |

+----+-------------+-------+------+---------------+---------+---------+-------+------+--------------------------+

1 row in set (0.00 sec)

这里where字句少了b字段,发现在查询时候只能用到索引a,这里就引出了一个最左前缀原理的概念.如果在查询的时候只是包含了索引定义顺序的某些字段,那么就只能用到复合索引的一部分.比如上面的查询,索引的定义顺序为(a,b,c).但是在查询的时候少了b字段,虽然Explain的key字段说明了使用到idx_abc索引,但是从ken_len中发现只是使用到了索引的第一列前缀.即只使用到了a.

当查询为:

mysql> explain select * from t1 where a=4 and b=4;

+----+-------------+-------+------+---------------+---------+---------+-------------+------+-------+

| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |

+----+-------------+-------+------+---------------+---------+---------+-------------+------+-------+

| 1 | SIMPLE | t1 | ref | idx_abc | idx_abc | 10 | const,const | 1 | NULL |

+----+-------------+-------+------+---------------+---------+---------+-------------+------+-------+

这个时候(a,b)是按照索引定义顺序出现的,所以这里可以用到(a,b)索引.即key_len=10

诡异的查询

根据上面说的,如果查询的时候没有按照索引定义的顺序来使用索引,那么只有左边的部分可以使用到索引.现在我们来看一个查询.

mysql> explain select * from t1 where b=4;

+----+-------------+-------+-------+---------------+---------+---------+------+------+--------------------------+

| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |

+----+-------------+-------+-------+---------------+---------+---------+------+------+--------------------------+

| 1 | SIMPLE | t1 | index | NULL | idx_abc | 15 | NULL | 1 | Using where; Using index |

+----+-------------+-------+-------+---------------+---------+---------+------+------+--------------------------+

1 row in set (0.00 sec)

这个查询只使用到了b,并没有使用a.按照上面的最左前缀原理,应该是没办法使用索引才对,但是这里的 ken_len为15说明使用到了索引.这个时候我们需要看type字段,发现这个查询type=index.而上一个为type=ref.这两个有什么区别?

type=index在解释Explain的使用的时候有讲到这种查询MySQL会对该索引进行扫描.这种只要where字句后面的字段为索引,或者复合索引的一部分,那么MySQL就会以index的方式进行扫描.但是这种扫描是很耗费性能的,因为他要从索引的第一个值开始扫描整张表.而且这种扫描是随机IO,因为这种扫描不是按照表的顺序扫描,而是按照索引的顺序扫描.这种查询的好处在于避免了排序.所以这里ken_len表示的不是使用索引进行查找数据,而是根据索引顺序进行扫描整张表

type=ref,这种查找就是我们平常说的使用索引能提高查询效率的查找,他是查找和扫描的混合体.这样讲可能有点难以理解.上栗子.

有索引idx_ac(a,b,c),那么索引在MySQL内部的构造类似上图,这有点类似order by a b c的感觉,A字段是绝对有序的,只有在A字段一样的情况下,才对B进行排序.最后才是C.

当我们查询where a=2 and c=5的时候,由于a是绝对有序的,所以查询时候通过索引可以马上查询到2,3行.之后c=5由于没有了中间的B,所以C相当于是无序的.这个时候,MySQL只能扫描2,3行来找到所需要的数据.这样子,我们也就解释了为什么查询where a=3 and c=5的时候只能用到索引a了.但是这个查询的的确确使用到了索引来加快查询速度.说着这个查询的type=ref.而不是type=index

现在,我们对上面的表增加一个字段alter table t1 add other int.这样子,表的结构就变成

CREATE TABLE `t1` (

`a` int(11) DEFAULT NULL,

`b` int(11) DEFAULT NULL,

`c` int(11) DEFAULT NULL,

`other` int(11) DEFAULT NULL,

KEY `idx_abc` (`a`,`b`,`c`)

)

这个时候我们再执行查询语句

mysql> explain select * from t1 where c=4;

+----+-------------+-------+------+---------------+------+---------+------+------+-------------+

| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |

+----+-------------+-------+------+---------------+------+---------+------+------+-------------+

| 1 | SIMPLE | t1 | ALL | NULL | NULL | NULL | NULL | 1 | Using where |

+----+-------------+-------+------+---------------+------+---------+------+------+-------------+

发现,这里type=ALL,按照上面Explain提到的,这个说明MySQL会去扫描全表,这是效率最低的情况.可是和上面的查询相比, 为什么我们查询的语句一样,查询的方式却从type=index变成了type=all?问题就在于我们添加了一个字段,添加一个字段之后让select * from t1 where c=4变成非覆盖索引的查询.在非覆盖索引查询的时候,没有遵循最左前缀原则的查询,只能扫描全表查询.

范围查询

mysql> explain select * from t1 where a=4 and b<5 and c=4;

+----+-------------+-------+-------+---------------+---------+---------+------+------+-----------------------+

| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |

+----+-------------+-------+-------+---------------+---------+---------+------+------+-----------------------+

| 1 | SIMPLE | t1 | range | idx_abc | idx_abc | 10 | NULL | 1 | Using index condition |

+----+-------------+-------+-------+---------------+---------+---------+------+------+-----------------------+

1 row in set (0.00 sec)

上面这种查询,虽然满足最左前缀,但是中间的B为范围查询(

索引与排序

索引在排序中的作用还是跟上面的一样.我们通过一个例子来分析下.

表结构如下

CREATE TABLE `t1` (

`a` int(11) DEFAULT NULL,

`b` int(11) DEFAULT NULL,

`c` int(11) DEFAULT NULL,

`other` int(11) DEFAULT NULL,

KEY `idx_abc` (`a`,`b`,`c`)

) ENGINE=InnoDB DEFAULT CHARSET=latin1

查询语句为:

mysql> explain select * from t1 where a=1 order by b;

+----+-------------+-------+------+---------------+---------+---------+-------+------+-------------+

| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |

+----+-------------+-------+------+---------------+---------+---------+-------+------+-------------+

| 1 | SIMPLE | t1 | ref | idx_abc | idx_abc | 5 | const | 1 | Using where |

+----+-------------+-------+------+---------------+---------+---------+-------+------+-------------+

1 row in set (0.00 sec)

这里,我们where字句只有a=1一个,所以key_len=5.但是Extra并没有出现using filesort的字段,说明这里没有进行排序.原因跟上面的类似:MySQL通过索引 查找到a=1的行数之后,由于a字段都相等,那么B字段已经是有序的,所以就不必再进行排序了.

如果查询换成:

mysql> explain select * from t1 where a=1 and b <3 order by c;

+----+-------------+-------+-------+---------------+---------+---------+------+------+---------------------------------------+

| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |

+----+-------------+-------+-------+---------------+---------+---------+------+------+---------------------------------------+

| 1 | SIMPLE | t1 | range | idx_abc | idx_abc | 10 | NULL | 1 | Using index condition; Using filesort |

+----+-------------+-------+-------+---------------+---------+---------+------+------+---------------------------------------+

1 row in set (0.00 sec)

这个时候在extra字段多了Using filesort,说明MySQL查询到所需要的字段之后还要进行排序,因为这个SQL语句B是范围查询,所以C是无序的.

mysql = 索引_深入MySQL索引相关推荐

  1. php和mysql联合_[转]mysql联合索引

    命名规则:表名_字段名 1.需要加索引的字段,要在where条件中 2.数据量少的字段不需要加索引 3.如果where条件中是OR关系,加索引不起作用 4.符合最左原则 https://segment ...

  2. mysql reverse 索引_降序索引和减轻索引扫描

    Descending indexing and loose index scan 降序索引和减轻索引扫描 Comments to my previous posts, especially this ...

  3. mysql5.6 函数索引_聊聊MySQL中的索引

    关于MySQL中的索引使用 索引是数据库优化中最常用也是最重要的手段之一,通过索引通常可以帮助用户解决大多数的SQL性能问题. 索引的存储分类: 1.B-Tree索引:最常见的索引类型,大部分引擎都支 ...

  4. mysql为什么使用b 树作为索引_为什么Mysql用B+树作为索引

    该篇文章已经投稿给公众号hollis 1.什么是索引 索引这个词,相信大多数人已经相当熟悉了.不过为了文章的完整性,这里再啰嗦一下.索引是一种数据结构,用于帮助我们在大量数据中快速定位到我们想要查找的 ...

  5. 覆盖索引与联合索引_浅析MySQL的索引覆盖和索引下推

    写在前面 在MySQL数据库中,索引对查询的速度有着至关重要的影响,理解索引也是进行数据库性能调优的起点,索引就是为了提高数据查询的效率.今天我们来聊聊在MySQL索引优化中两种常见的方式,索引覆盖和 ...

  6. mysql非主键索引_主键索引和非主键索引的区别

    1. 什么是最左前缀原则? 以下回答全部是基于MySQL的InnoDB引擎 例如对于下面这一张表 如果我们按照 name 字段来建立索引的话,采用B+树的结构,大概的索引结构如下 如果我们要进行模糊查 ...

  7. 建立唯一索引后mysql策略_【MySQL】MySQL索引背后的之使用策略及优化【转】

    另外很不错的对于索引及索引优化的文章: 索引的使用 示例数据库 为了讨论索引策略,需要一个数据量不算小的数据库作为示例.本文选用MySQL官方文档中提供的示例数据库之一:employees.这个数据库 ...

  8. mysql 值很少的索引_关于 Mysql 字段值分布很少的字段要不要加索引的问题.

    我看到很多 mysql 索引的文章,都提到了,说如果某个字段的值分布范围很少(大量重复值),是不需要建立索引的. 但是我实际测试情况: user_type_id字段有索引: create table ...

  9. mysql pt工具 加索引_[转]MySQL中如何为连接添加索引

    SELECT * FROM tblA, tblB, tblC WHERE tblA.col1 = tblB.col1 AND tblA.col2 = tblC.col1; explain的结果如下: ...

最新文章

  1. 迁移学习之DenseNet121(121层),DenseNet169(169层),DenseNet201(201层)(图像识别)
  2. angular模拟web API
  3. JZOJ 5678. 【GDOI2018Day2模拟4.21】果树
  4. 三十五、Scrapy 中的杂知识总结和代理池的编写
  5. centos snmp配置_Cacti1.2.16最新版安装和配置(Shell一键安装)
  6. 简述旋转编码器的工作原理_什么是编码器,编码器工作原理介绍
  7. c语言语言教程0基础_C语言基础
  8. Tensorflow2.0模型构建与训练
  9. SLB访问日志分析:基于客户端来源和HTTP状态码的实践
  10. 完成css的切图 图片任意,css切图是什么意思
  11. spss java vm_如何解决spss无法创建java虚拟机的问题
  12. win10电脑怎么将html网页做成壁纸,手把手教你win10动态桌面怎么设置
  13. 高通CSRA6640单芯片DDFA放大器解决方案
  14. Android Studio第九课(学习打卡Day11)
  15. python如何截取视频中的某一段
  16. 【SDCC讲师专访】腾讯潘安群:腾讯云金融级数据库TDSQL分析
  17. vue引入JQ的方法
  18. 关于《管理:技艺之精髓》一书中提到的管理任务和工具
  19. Excel打开CSV格式,大数字乱码情况
  20. 华展云-让展览更高效 2017年第十九届中国国际医疗器械及口腔器材(江苏)博览会 2017年第十届中国检验医学及输血用品(江苏)博览会会刊(参展商名录)

热门文章

  1. Netty工作笔记0068---Protobuf机制简述
  2. 微服务架构工作笔记003---了解认识google Kubernetes 容器管理
  3. 数据库工作笔记017---还记得Oracle悲观锁和乐观锁嘛?以及hibernate对乐观悲观锁的封装
  4. VB.NET工作笔记004---查看电脑已经安装了哪些COM组件,可以用个OleViewer.zip
  5. 杭电1229 还是A+B
  6. 杭电1420 Prepared for New Acmer
  7. C语言和设计模式(工厂模式)
  8. 联想计算机主机编号,联想如何查找主机编号
  9. mac php配置和扩展,mac 下安装php 以及 配置扩展!!!!!
  10. php 5.6.21连接mysql_IIS 7.5 + PHP-5.6.3 + mysql-5.6.21.1