Mysql索引是什么“鬼”

前言

索引有不少种,hash索引,B树索引,B+树索引,全文索引等。Mysql支持多种存储引擎,多种存储引擎对索引的支持也各不相同。本文探究Mysql为何使用B+树来做为索引的数据结构,索引的原理已经Sql中索引的优化。
Mysql官方对索引的定义是:索引(Index)是帮助Mysql高效获取数据的数据结构。提取句子主干就是:索引是数据结构。mysql

索引的原理

索引的目的

索引的目的在于提升查询或检索效率。例如咱们要在字典中查询“mysql”这个单词,是否是先要查询m开头的单词表,而后在查询第二个字母为y的单词,而后缩小范围继续找,知道找到“mysql”这个单词为止或者查无此词。这就好像咱们沿着一个树从树根开始找,沿着主干,树干,到最后的末梢,走了其中的一条路径。这比一个查询一个链表的结构,从头找到尾,在大多数状况下,效率要高得多。算法

Mysql的索引为何是B+树

为何不用普通的二叉树,这里就没必要多说了,由于对于大的数据量,二叉树的高度过高,索引的效率低下。这里主要说明为何不用B树(B-树就是B树),而是用B+树。sql

B树(B-树)介绍

咱们都知道二叉树查询的时间复杂度为O(logN),查询效率已经够高了,但为何还要有B树和B+树呢?答案是磁盘IO。咱们都知道,IO操做的效率很低,当有存储的有很大的数据量,查询的时候,咱们不可能把所有数据都加载到内存中,只能逐一加载磁盘页,每一个磁盘页对应树的节点,形成大量的磁盘IO操做(最坏状况下,磁盘IO操做次数是树的高度),平衡二叉树因为树的高度太大形成磁盘IO读写过于频繁,从而致使效率低下,因此多路查找树-B树/B+树应运而生。
下面是一个三阶的B树(实际中节点元素不少)数据库

B树有如下特色:

  • 在一个节点中存放着数据和指针,且相互间隔
  • 在同一个节点中,key是增序的
  • 若是一个节点最左边的指针不为空,则它指定的节点左右的key小于最左边的key。中间的指针指向的节点的key位于相邻两个key的中间。
  • B树中不一样节点存放的key和指针可能数量不一致,可是每一个节点的域和上限是一致的,因此在实现中B树每每对每一个节点申请同等大小的空间
  • 每一个非叶子节点由n-1个key和n个指针组成,其中d<=n<=2d

B+树

B+树有如下特色:缓存

  • 内节点不存储data,只存储key和指针,叶子结点不存储指针,只存储key和data
  • 内节点和叶子结点的大小不一样,由于存储的东西不一样
  • 每一个非叶子结点的指针上限为2d而不是2d+1
  • 由于节点内部没有data,因此有更多的空间放key,因此B+树的出度通常比B树要大,而对于必定的数据,出度大的话,树的深度就小,因此B+树的检索效率比B树高

为何B+树比B树更适合Mysql索引

  • B+树的磁盘读取代价低:由于B+树的非叶子结点没有存储数据,因此若是把全部同一内部节点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读内存中的须要查找的关键字也就越多。相对来讲IO读写次数也就下降了。
  • B+树的查询效率更稳定:因为B+树的分支结点并非最终指向文件内容的结点,只是叶子结点的索引,因此任意关键字的查找都必须从根节点走向分支结点,查询路径相同。但B树的分支结点保存有数据,因此查询路径可能不一样。
  • B+树便于执行扫库操做:因为B+树的数据都存储在叶子节点上,分支节点均为索引,方便扫库,只需扫一遍叶子便可。可是B树在分支节点上都保存着数据,要找到具体的顺序数据,须要执行一次中序遍从来查找

Mysql的索引实现

咱们知道Mysql有两种经常使用的存储引擎,MyISAM和InnoDB,这两种存储引擎对索引的实现方式是不一样的。安全

MyISAM索引实现

MyISAM使用B+树做为索引的结构,叶子结点的data域存放的是数据记录的地址。 bash

上图中是以Col1做为主键的MyISAM主索引的示意图。能够看到,组下面一层叶子结点的data域存放的是数据记录的地址。若是咱们在字段Col2上建一个辅助索引,那么索引的结构以下:

MyISAM索引检索算法是这样的,首先按照B+树的搜索算法查询索引,若是指定的key存在,则取出data域的值,而后用data域的地址查询数据记录。MyISAM的索引方式也叫“非汇集的”,跟InnoDB的“汇集索引”相区分,由于数据记录和索引不在一块儿。

InnoDB索引实现

InnoDB的索引实现方式与MyISAM的索引实现方式的区别有两个:
第一,InnoDB的数据文件自己就是索引文件。在InnoDB中,数据文件自己就是按B+树组织的一个索引结构,并且是主索引结构。数据和索引在一块儿,叶子结点保存了完整的数据记录,这种索引叫作汇集索引。由于InnoDB的数据文件自己要按主键汇集,因此InnoDB要求表必须有主键(MyISAM能够没有),若是没有显式指定,则MySQL系统会自动选择一个能够惟一标识数据记录的列做为主键,若是不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段做为主键,这个字段长度为6个字节,类型为长整形。服务器

第二,InnoDB辅助索引的data域存储的是相应记录主键的值而不是地址。如图,下图是定义在Col3上的一个辅助索引的示意图。叶子结点存储了col3的值和对应的主键col1的值。

索引优化

墙裂建议使用自增主键

在使用InnoDB做为存储引擎时,若是没有特殊须要,请永远是用一个与业务无关的自增字段做为主键,并且这个字段长度不宜过大。为何?InnoDB使用汇集索引,数据记录自己存放在主索引(B+树)的叶子结点上,这就要求同一个叶子结点(大小为一个内存页或磁盘页)的数据记录按主键顺序存放,每当一条新的记录插入时,mysql会根据其主键将其插入适当的节点和位置,若是页面达到装载因子(InnoDB默认为15/16),则开辟一个新的页(节点)。若是使用自增主键,那么每次插入新的记录,记录就会顺序插入到当前节点的下一个位置。这样就会造成一个紧凑的索引结构,每次插入不须要移动已有数据,所以效率很高。以下图:网络

若是使用非自增主键(例如身份证号或学号这种无序字符串),每次插入主键近似随机,每次记录都要插入到现有索引页的中间的某个位置,这时不得不移动元素来完成插入,增长了开销。以下图:数据结构

索引的最左前缀原则

联合索引:mysql能够将多个列按照顺序做为一个索引,这种索引叫作联合索引。
索引的最左匹配原则是:假如索引列分别为A,B,C,顺序也是A,B,C,那么:

  • 查询的时候,若是查询【A】,【A,B】,【A,B,C】,可使用索引查询。
  • 若是查询的时候,查询【A,C】,因为中间缺失了B,那么C这个索引是用不到的,只能用到A索引。
  • 若是查询的时候,查询【B】,【B,C】或【C】,因为缺失了最左前缀A,那么是用不到这个联合索引的,除非有其余索引。
  • 若是查询的时候使用范围查询,而且是最左前缀,那么能够用到索引,可是范围后面的字段没法用到索引。

这个原则能够结合索引的原理来理解:Mysql索引是B+树这种复合结构,当索引是联合索引,好比【name,age,sex】时,B+树是按照从左到右的顺序创建索引树的。当(张三,20,M)这样的数据来检索时,B+树会优先根据name来肯定下一步的搜索方向,若是name相同再比较name和sex,最后获得检索的数据。但当(20,M)这样的数据来的时候,mysql就不知道该查哪一个节点,由于创建索引的时候,name就是第一个比较因子,必须先根据name去肯定下一步去哪里搜索。当(张三,M)这样的数据来时,能够根据name是“张三”,来肯定下一步的搜索,而后再去匹配性别是“M”的数据,所以只能用到联合索引中name这个索引。

其余原则

一、尽可能选择区分度高的列做为索引,区分度公式:count(distinct col)/count(*),表示字段不重复的比例,比例越大,咱们扫描的记录数就越少,惟一性的列的区分度为1。这就是为何不建议在状态,性别这样区分度很小的列上创建索引的缘由。
二、索引列在sql语句中不能参与运算,不然会致使索引失效。例如from_unixtime(create_time) = ’2014-05-29’就不能使用到索引,缘由很简单,b+树中存的都是数据表中的字段值,但进行检索时,须要把全部元素都应用函数才能比较,显然成本太大。应该改为create_time = unix_timestamp(’2014-05-29’);
三、联合索引比单个索引的性价比更高。例如,创建【A,B,C】这个联合索引,至关于创建了【A】,【A,B】,【A,B,C】这三个索引。这就要求咱们尽可能的扩展索引而不是新建索引,具体状况还需具体分析。
四、频繁进行查询的字段应该新建索引,与其余表进行关联的字段能够考虑新建索引,查询中排序的字段能够考虑创建索引以提升排序的效率(这里举个例子,不少时候查询记录但愿按照建立时间倒序返回,一般有人会这样作order by create_time desc,可是若是create_time不是索引,而这个表有自增主键id,那么order by id desc返回结果同样,可是效率会提升)。

Mysql优化

致使sql执行慢的缘由

一、硬件问题:如网络速度慢,内存不足,I/O吞吐量小,磁盘空间满了等。
二、没有使用索引或者索引失效。
三、数据过多(分库分表)。
四、服务器或参数设置不当。

分析解决慢sql方法

一、先观察,开启慢查询日志,设置相应的阈值(好比超过3秒就是慢sql),再生产环境跑个一天,看看哪些sql比较慢。
二、explain和慢sql分析,好比sql语句写的很差,没有使用索引或者索引失效,或者sql语句太过复杂,关联查询和嵌套子查询太多等等。
三、Show Profile是比explain更近一步的执行细节,能够查询到执行每个SQL都干了什么事,这些事分别花了多少秒。
四、找DBA或者运维对Mysql进行服务器的参数调优。

配置优化

基本配置

  • innodb_buffer_pool_size:这是安装完InnoDB后第一个应该设置的选项。缓冲池是数据和索引缓存的地方:这个值越大越好,这能保证你在大多数的读取操做时使用的是内存而不是硬盘。典型的值是5-6GB(8GB内存),20-25GB(32GB内存),100-120GB(128GB内存)。
  • innodb_log_file_size:这是redo日志的大小。redo日志被用于确保写操做快速而可靠而且在崩溃时恢复。一直到MySQL 5.5,redo日志的总尺寸被限定在4GB(默承认以有2个log文件)。这在MySQL5.6里被提升了。若是你知道你的应用程序须要频繁的写入数据而且你使用的时MySQL5.6,一开始就能够设置成4G。
  • max_connections:若是你常常看到'Too many connections'错误,是由于max_connections的值过低了由于应用程序没有正确的关闭数据库链接,你须要比默认的151链接数更大的值。max_connection值被设高了(例如1000或更高)以后一个主要缺陷是当服务器运行1000个或更高的活动事务时会变的没有响应。

InnoDB配置

  • innodb_file_per_table:这项设置告知InnoDB是否须要将全部表的数据和索引存放在共享表空间里(innodb_file_per_table = OFF) 或者为每张表的数据单独放在一个.ibd文件(innodb_file_per_table = ON)。每张表一个文件容许你在drop、truncate或者rebuild表时回收磁盘空间。这对于一些高级特性也是有必要的,好比数据压缩。你不想让每张表一个文件的主要场景是:有很是多的表(好比10k+)。
  • innodb_flush_log_at_trx_commit:默认值为1,表示InnoDB彻底支持ACID特性。当你的主要关注点是数据安全的时候这个值是最合适的,好比在一个主节点上。可是对于磁盘(读写)速度较慢的系统,它会带来很巨大的开销,由于每次将改变flush到redo日志都须要额外的fsyncs。将它的值设置为2会致使不太可靠(reliable)由于提交的事务仅仅每秒才flush一次到redo日志,但对于一些场景是能够接受的,好比对于主节点的备份节点这个值是能够接受。
  • innodb_flush_method:这项配置决定了数据和日志写入硬盘的方式。通常来讲,若是你有硬件RAID控制器,而且其独立缓存采用write-back机制,并有着电池断电保护,那么应该设置配置为O_DIRECT;不然,大多数状况下应将其设为fdatasync(默认值)
  • innodb_log_buffer_size:这项配置决定了为还没有执行的事务分配的缓存。其默认值(1MB)通常来讲已经够用了,可是若是你的事务中包含有二进制大对象或者大文本字段的话,这点缓存很快就会被填满并触发额外的I/O操做。

执行计划Explain

准备数据

CREATE TABLE `user_info` (

  `id`   BIGINT(20)  NOT NULL AUTO_INCREMENT,

  `name` VARCHAR(50) NOT NULL DEFAULT '',

  `age`  INT(11)              DEFAULT NULL,

  PRIMARY KEY (`id`),

  KEY `name_index` (`name`)

)ENGINE = InnoDB DEFAULT CHARSET = utf8;

 

INSERT INTO user_info (name, age) VALUES ('xys', 20);

INSERT INTO user_info (name, age) VALUES ('a', 21);

INSERT INTO user_info (name, age) VALUES ('b', 23);

INSERT INTO user_info (name, age) VALUES ('c', 50);

INSERT INTO user_info (name, age) VALUES ('d', 15);

INSERT INTO user_info (name, age) VALUES ('e', 20);

INSERT INTO user_info (name, age) VALUES ('f', 21);

INSERT INTO user_info (name, age) VALUES ('g', 23);

INSERT INTO user_info (name, age) VALUES ('h', 50);

INSERT INTO user_info (name, age) VALUES ('i', 15);

 

CREATE TABLE `order_info` (

  `id`           BIGINT(20)  NOT NULL AUTO_INCREMENT,

  `user_id`      BIGINT(20)           DEFAULT NULL,

  `product_name` VARCHAR(50) NOT NULL DEFAULT '',

  `productor`    VARCHAR(30)          DEFAULT NULL,

  PRIMARY KEY (`id`),

  KEY `user_product_detail_index` (`user_id`, `product_name`, `productor`)

)ENGINE = InnoDB DEFAULT CHARSET = utf8;

 

INSERT INTO order_info (user_id, product_name, productor) VALUES (1, 'p1', 'WHH');

INSERT INTO order_info (user_id, product_name, productor) VALUES (1, 'p2', 'WL');

INSERT INTO order_info (user_id, product_name, productor) VALUES (1, 'p1', 'DX');

INSERT INTO order_info (user_id, product_name, productor) VALUES (2, 'p1', 'WHH');

INSERT INTO order_info (user_id, product_name, productor) VALUES (2, 'p5', 'WL');

INSERT INTO order_info (user_id, product_name, productor) VALUES (3, 'p3', 'MA');

INSERT INTO order_info (user_id, product_name, productor) VALUES (4, 'p1', 'WHH');

INSERT INTO order_info (user_id, product_name, productor) VALUES (6, 'p1', 'WHH');

INSERT INTO order_info (user_id, product_name, productor) VALUES (9, 'p8', 'TE');
复制代码

执行explain看看,索引使用状况在possible_keys、key和key_len这三列。

分析explain

  • id
    id相同,执行顺序由上而下

id不一样,值越大越先执行

  • select_type

select_type总共有如下几种类型:
一、SIMPLE:表示查询不使用UNION或子查询
二、PRIMARY:表示此查询是最外层的查询
三、SUBQUERY:表示此查询是第一个查询
四、UNION:表示此查询是UNION第二或随后的查询 五、DEPENDENT UNION:UNION中的第二个或后面的查询语句,取决于外面的查询 六、UNION RESULT:UNION的结果 七、DEPENDENT SUBQUERY:子查询中的第一个SELECT,取决于外面的查询,即子查询依赖于外面查询的结果 八、DERIVED:衍生,表示导出表的SELECT

  • table table表示查询涉及的表或衍生的表

id=1的table derived2表示是由id=2的u和o衍生出来的

  • type
    type字段比较重要,它是判断查询是否高效的重要依据。
    一、system:表中只有一条数据,这种类型是特殊的const类型
    二、const:针对主键或惟一索引的等号条件进行扫描,最多只返回一条数据,查询速度极快,由于它仅仅读取一次便可。
    三、eq_ref:此类型一般出如今多表join,表示对于前表的每个结果,都只能匹配到后表的一行结果,且查询的比较操做一般是=,查询效率较高。
    四、ref:此类型一般是多表join,针对非惟一索引,或者非主键索引,或者使用了最左前缀规则的索引。
    五、range:表示使用索引范围查询,经过索引字段范围获取表中部分数据记录,这个类型一般出如今 =, <>, >, >=, <, <=, IS NULL, <=>, BETWEEN, IN() 操做中。
    六、index:表示全索引扫描,和ALL类型相似,只不过All类型是全表扫描,index是扫描全部的索引,而不扫描数据。index 类型一般出如今:所要查询的数据直接在索引树中就能够获取到,而不须要扫描数据。当是这种状况时,Extra 字段 会显示 Using index。
    七、ALL:表示全表扫描,性能最差。当数据量大的时候,对数据库会是巨大的灾难。
  • possible_keys
    它表示mysql在查询时,可能使用到的索引。注意:有些索引即便在possible_keys中出现,也不表示真正的会用到,mysql 在查询时具体使用了哪些索引,由 key 字段决定。
  • key
    这是mysql在查询时真正用到的索引
  • key_len
    表示mysql使用索引的字节数,这个字段能够判断组合索引是否彻底被使用
  • ref
    这个字段表示索引的哪一列被使用了,若是可能的话,会是一个常量。
  • rows
    这也是判断sql性能好坏的一个重要字段。mysql 查询优化器根据统计信息,估算 sql要查找到结果集须要扫描读取的数据行数。原则上来讲,rows越小查询效率越高。
  • extra
    explain 中的不少额外的信息会在 extra 字段显示,常见的有如下几种:
    一、using filesort :表示 mysql 需额外的排序操做,不能经过索引顺序达到排序效果。通常有 using filesort都建议优化去掉,由于这样的查询 cpu 资源消耗大。
    二、using index:覆盖索引扫描,表示查询在索引树中就可查找所需数据,不用扫描表数据文件,每每说明性能不错。
    三、using temporary:查询有使用临时表, 通常出现于排序, 分组和多表 join 的状况, 查询效率不高,建议优化。
    四、using where :代表使用了where过滤。

总结

常见的索引原则:

  • 选择惟一性索引:惟一性索引的值是唯一的,能够更快速的经过该索引肯定某条记录。
  • 为常常须要排序、分组和联合操做的字段创建索引。
  • 为常做为查询条件的字段创建索引。
  • 限制索引的数目:索引不是越多越好,太多的索引会使插入和更新变慢,由于须要维护索引树。
  • 尽可能使用数据量少的索引,若是索引的值很长,那么查询的速度会受到影响。
  • 最左前缀匹配原则,是很是重要的原则。
  • 尽可能选择区分度高的列做为索引,区分度的公式是表示字段不重复的比例。
  • 索引列不能参与计算,保持列“干净”:带函数的查询不参与索引。
  • 删除再也不使用或不多使用的索引。
  • 尽可能的扩展索引,而不是新建索引,能使用组合索引就不要新建索引。
相关文章
相关标签/搜索