深入MySQL索引

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 + 10*3=32字节

key&&possible_key

  • key字段表示当前搜索使用到的索引
  • possible_可以表示搜索之前MySQL估计可能使用到的索引

type

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

  • ALL : 表示MySQL必须扫描整张表才能找到所需要的行.注意这里的扫描是指从表的第一行数据开始扫描.
  • index : 跟ALL的一样 需要扫描全表才能找到需要的行,但是不同的是扫描的顺序不是从表的第一行开始,而是根据索引的顺序扫描的.这个有一点好处是避免了排序,因为索引本身已经是有序的.
  • range : 这种查询跟index不同的地方在于不必进行全表扫描这种类型的查询一般在where字句中带有<, between的时候出现.所以这种情况下,只要扫描部分索引就可以了.
  • 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为范围查询(<, between),那么范围查询之后的字段是用不到索引的,所以可以看出.这里的ken_len为10.其原理跟上面的一样, 由于B是范围查询,所以相当于C是无序的.这个时候只能使用扫描的方式来查找C.

索引与排序

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

表结构如下

 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是无序的.

时间: 2024-10-11 12:52:11

深入MySQL索引的相关文章

MySQL索引基本应用[转]

原文地址:http://www.php100.com/html/webkaifa/database/Mysql/2010/0409/4279.html 索引是快速搜索的关键.MySQL索引的建立对于MySQL的高效运行是很重要的.下面介绍几种常见的MySQL索引类型. 在数据库表中,对字段建立索引可以大大提高查询速度.假如我们创建了一个 mytable表: CREATE TABLE mytable(   ID INT NOT NULL,    username VARCHAR(16) NOT N

深入浅出分析MySQL索引设计背后的数据结构

在我们公司的DB规范中,明确规定: 1.建表语句必须明确指定主键 2.无特殊情况,主键必须单调递增 对于这项规定,很多研发小伙伴不理解.本文就来深入简出地分析MySQL索引设计背后的数据结构和算法,从而可以帮你释疑如下问题: 1.为什么innodb表需要主键? 2.为什么建议innodb表主键是单调递增? 3.为什么不建议innodb表主键设置过长? B-tree(多路搜索树,并不是二叉的)是一种常见的数据结构.使用B-tree结构可以显著减少定位记录时所经历的中间过程,从而加快存取速度.B通常

mysql索引介绍

在数据表中对字段建立索引将大大提高查询的速度: 例如:select * from mytable where username='admin' 如果在列username上建立了索引,只需要一次就可以找到该记录 一.mysql索引的类型: 1.普通索引 创建:create index indexname on mytable(username) 删除:drop index [indexname] on mytable 2.唯一索引 特点:索引列值必须唯一,但允许有null值 创建:create u

mysql索引总结----mysql 索引类型以及创建

关于MySQL索引的好处,如果正确合理设计并且使用索引的MySQL是一辆兰博基尼的话,那么没有设计和使用索引的MySQL就是一个人力三轮车.对于没有索引的表,单表查询可能几十万数据就是瓶颈,而通常大型网站单日就可能会产生几十万甚至几百万的数据,没有索引查询会变的非常缓慢.还是以WordPress来说,其多个数据表都会对经常被查询的字段添加索引,比如wp_comments表中针对5个字段设计了BTREE索引. 一个简单的对比测试 以我去年测试的数据作为一个简单示例,20多条数据源随机生成200万条

MySQL索引

mysql索引  注意:创建索引的基本原则   索引要建在使用比较多的字段上   尽量不要在相同值比较多的列建立索引,比如性别.年龄等字段   对于经常进行数据存取的列不要建立索引   对于有外键引用的表,在主键和外键上建立索引   1.普通索引 索引创建的三种方式===== 主键是一种特殊索引 表已创建  ===help create index; create index ipaddr_idx on test(ipaddr); mysql> create index ipaddr_idx o

mysql 索引和查询优化

对于任何DBMS,索引都是进行优化的最主要的因素.对于少量的数据,没有合适的索引影响不是很大,但是,当随着数据量的增加,性能会急剧下降.如果对多列进行索引(组合索引),列的顺序非常重要,MySQL仅能对索引最左边的前缀进行有效的查找. 例如:假 设存在组合索引it1c1c2(c1,c2),查询语句select * from t1 where c1=1 and c2=2能够使用该索引.查询语句select * from t1 where c1=1也能够使用该索引.但是,查询语句select * f

[转载]MySQL索引原理与慢查询优化

好文,以防丢失,故转之,另对排版做简单优化.原文地址:http://ourmysql.com/archives/1401 索引目的 索引的目的在于提高查询效率,可以类比字典,如果要查"mysql"这个单词,我们肯定需要定位到m字母,然后从下往下找到y字母,再找到剩下的sql.如果没有索引,那么你可能需要把所有单词看一遍才能找到你想要的,如果我想找到m开头的单词呢?或者w开头的单词呢?是不是觉得如果没有索引,这个事情根本无法完成? 索引原理 除了词典,生活中随处可见索引的例子,如火车站的

MySQL索引使用方法和性能优化

关于MySQL索引的好处,如果正确合理设计并且使用索引的MySQL是一辆兰博基尼的话,那么没有设计和使用索引的MySQL就是一个人力三轮车.对于没有索引的表,单表查询可能几十万数据就是瓶颈,而通常大型网站单日就可能会产生几十万甚至几百万的数据,没有索引查询会变的非常缓慢.还是以WordPress来说,其多个数据表都会对经常被查询的字段添加索引,比如wp_comments表中针对5个字段设计了BTREE索引. 一个简单的对比测试 以我去年测试的数据作为一个简单示例,20多条数据源随机生成200万条

mysql索引实现

原文地址:http://blog.codinglabs.org/articles/theory-of-mysql-index.html MySQL索引实现 在MySQL中,索引属于存储引擎级别的概念,不同存储引擎对索引的实现方式是不同的,本文主要讨论MyISAM和InnoDB两个存储引擎的索引实现方式. MyISAM索引实现 MyISAM引擎使用B+Tree作为索引结构,叶节点的data域存放的是数据记录的地址.下图是MyISAM索引的原理图: 图8 这里设表一共有三列,假设我们以Col1为主键

MySQL— 索引

目录 一.索引 二.索引类型 三.索引种类 四.操作索引 五.创建索引的时机 六.命中索引 七.其它注意事项 八.LIMIT分页 九.执行计划 十.慢查询日志 一.索引 MySQL索引的建立对于MySQL的高效运行是很重要的,索引可以大大提高MySQL的检索速度. 打个比方,如果合理的设计且使用索引的MySQL是一辆兰博基尼的话,那么没有设计和使用索引的MySQL就是一个人力三轮车. 索引分单列索引和组合索引.单列索引,即一个索引只包含单个列,一个表可以有多个单列索引,但这不是组合索引.组合索引