:接触mysq也有两年左右的时间了,但是对该数据库的理解自认还比较初级,看过很多文章,也看过一些相关的书籍,依然小白。。。。(这里个人总结是两点主要原因:1.对mysql的学习大部分都是源于看一些杂七杂八的文章,很多文章本身表述有误或不准确。2.实操较少,平时增删改查索引的使用很常规,缺少钻研精神 3.第三点也就是写这篇文章的主要目的,看到的东西经常是看了就看了,过一段时间就忘记了,然后反复重复这样的经历,时间花的不少,但是这样永远不会对mysql系统掌握,故而打算通过文章把对mysql的理解落地,理解多少写多少,可能会东一点西一点比较乱,日后逐渐完善吧)

对索引的认识:

什么是索引?

我最初的理解是把索引当场一本字典的目录。

假如一本字典没有目录,那我们想查到某个字只能从头找到尾。

所以才会对字典按照页码来进行划分,并且在字典的最开始几页,有相应的目录可以让我们知道目标字在哪一页,从而快速定位到目标。

而且根据需求的不同,一般用过字典的小伙伴都知道,有按照拼音开头的目录,有按照偏旁部首开头的目录,等等。。。 其实和mysql中的 主键索引 联合索引 等等种类的索引是异曲同工的。

数据结构:

mysql索引使用的数据结构是B+树,目前大部分数据库系统都采用 B+Tree 或 B-Tree 这两种数据结构,本文不打算详解其中原因(对两种数据结构细节感兴趣的可以参考其他文章)

简单来说,索引文件一般是存在于硬盘当中,由于数据量大无法全部一次加载进内存。而磁盘的IO读取代价相比于内存读取代价要高很多倍,所以我们在选择数据结构上面,最重要的一点就是尽可能减少对磁盘io的操作。

数组:索引文件无法一次加载进内存

链表:查询需要从头到尾的查询

上面的两种B树特点决定了他们可以保证尽可能少的进行io操作,而且相对于平衡二叉树、二叉搜索树等数据结构,树的高度要低很多,查询对应的io次数更少。

而B+Tree和B-Tree的区别主要是前者的非叶子节点不存储数据,目的是能够存储更多的索引,保证整颗树更“矮”,但是想要找到数据必须进行树的高度次IO操作(即B+Tree的高度为3,则需要进行3次IO操作,在叶子节点上找到目标数据)

而B-Tree不论叶子节点还是非叶子节点都存放数据,好处是可能在没有到达叶子结点的时候就找到了目标数据

但是B-Tree如果想要存放和B+Tree相同量的索引,则必须让树的高度增加,也就是增加io次数,所以B+Tree相对更稳定

聚集索引和非聚集索引:

在mysql当中,不同的存储引擎对索引的实现是不同的,本文介绍常见的两种mysql存储存储引擎对索引的实现,即MyISAM存储引擎和InnoDB存储引擎,二者对索引的实现分别为非聚集索引(非聚簇索引)和聚集索引(非聚簇索引),这也是最常见的两种索引的实现

1、MyISAM索引实现:

MyISAM引擎使用的是非聚集索引的形式,简单来说是索引和数据是分开存放的,索引存放的是数据文件的地址,我们需要先找到索引,然后再通过索引找到数据

初识mysql索引 - 小白篇-LMLPHP

MyISAM引擎中,主键和一般索引在结构上没有区别,只是主键必须是唯一的,但是在查询时,普通索引和主键索引都是需要先获得数据文件地址,再去找到相应的数据

2、InnoDB索引实现

该存储引擎使用的是聚集索引,该索引方式最明显的特点就是主键索引和数据是在一起的(同一个文件中),可以理解为找到了主键索引也就找到了数据,不需要二次查找

初识mysql索引 - 小白篇-LMLPHP

而该索引结构当中的普通索引,想找到数据,也必须通过主键索引进行二次查找才可以

即:通过主键索引查找数据,查找一次 ,而通过普通索引查找数据,则需要普通索引找到主键索引,通过主键索引找到数据,也就是所谓的二次查找

如图为:mysql中两种索引方式的对比

初识mysql索引 - 小白篇-LMLPHP

 索引的使用策略,以及需要注意的点:

目前使用的存储引擎最多的就是InnoDB,我们的索引形式一般也为聚集索引,也可以称之为主键索引,以下讨论的索引使用策略基于该存储引擎

1、最左前缀原理:以联合索引举例比较容易理解:一般来说,联合索引即多个字段共同组成的索引,如 student表中:<stu_num, stu_age, date>学号 年龄  日期,三个字段建立的联合索引

    我们在查询时正常来说也必须按照建立联合索引的顺序来进行查找  如:

select * from student where stu_num = 1 and stu_age = 22 and date = '1993-11-11'; 

这样是可以充分利用联合索引的

但是由于mysql会在查询时对我们的sql语句进行优化 ,所以即便是我们写的顺序是乱的,mysql依然会把sql调整为正确顺序 

select * from student where stu_age = 22 and stu_num = 1  and date = '1993-11-11'; 

如果我们只用联合索引当中的某个字段来查询时,只能用到联合索引的一部分

select * from student where stu_num = 1

但是如果我们用到的字段中间断开了,即缺少中间的某个字段,则后面的data列的索引无法触发

select * from student where stu_num = 1 and date = '1993-11-11'; 

如果我们没有用到第一列的索引,则不会触发任何索引

select * from student where stu_age = 22 and date = '1993-11-11'; 

2、范围查询只能用到一次索引  

3、 在查询条件中含有表达式或者函数,则无法用到索引,理由很简单,MySQL还没有智能到自动优化常量表达式的程度,因此在写查询语句时尽量避免表达式出现在查询中,而是先手工私下代数运算,转换为无表达式的查询语句。

索引的选择性:

在我们选择某一列数据作为索引的时候,有一个标准,称之为索引选择性,所谓索引的选择性(Selectivity),是指不重复的索引值(也叫基数,Cardinality)与表记录数(#T)的比值

在这里我想通俗的解释一下。我们建立索引的目的是要在查找的时候尽可能的降低我们的搜索范围。

比如student数据表中有10000条数据,存储50个班级的学生,

我们如果不用任何索引来查找一个学生,那我们需要定位的范围是10000条数据

我们通过学号这个唯一的字段作为索引,我们可以锁定该数据在某一行。

如果我们通过班级作为索引我们可以锁定该数据在200条数据以内

如果我们通过性别作为索引,(假设只有男女两种性别,并且男女比例1:1),我们可以锁定该数据在5000条数据范围内。

我们所说的选择性表达的含义就是这样,如果在牺牲了大量空间代价建立的索引,在查找时只能将搜索范围缩小一半,那我们依然需要遍历5000条数据,索引的意义也就不大。

故而我们在建立索引时,需要尽可能的在查找时缩小查询范围

是否要选择与业务无关的自增主键作为索引? 

在使用InnoDB存储引擎时,如果没有特别的需要,请永远使用一个与业务无关的自增字段作为主键。

经常看到有帖子或博客讨论主键选择问题,有人建议使用业务无关的自增主键,有人觉得没有必要,完全可以使用如学号或身份证号这种唯一字段作为主键。不论支持哪种论点,大多数论据都是业务层面的。如果从数据库索引优化角度看,使用InnoDB引擎而不使用自增主键绝对是一个糟糕的主意。

上文讨论过InnoDB的索引实现,InnoDB使用聚集索引,数据记录本身被存于主索引(一颗B+Tree)的叶子节点上。这就要求同一个叶子节点内(大小为一个内存页或磁盘页)的各条数据记录按主键顺序存放,因此每当有一条新的记录插入时,MySQL会根据其主键将其插入适当的节点和位置,如果页面达到装载因子(InnoDB默认为15/16),则开辟一个新的页(节点)。

如果表使用自增主键,那么每次插入新的记录,记录就会顺序添加到当前索引节点的后续位置,当一页写满,就会自动开辟一个新的页。如下图所示:

初识mysql索引 - 小白篇-LMLPHP

如果我们选择一个身份证号或者学号之类的字段作为索引,则每次生成的主键是随机的,可能会有大量的叶子节点的裂变,同时维护索引的代价也大大增加

参考文献:http://blog.codinglabs.org/articles/theory-of-mysql-index.html

初识mysql索引 - 小白篇-LMLPHP

11-07 00:10