文献参考连接:https://www.cnblogs.com/wuzhenzhao/p/10341114.html
最近在找工作中,复习了下mysql索引相关知识, 整理的比较杂乱:
首先说下一常用的索引类型:一般是B-tree和Hash
B-tree简要说明
每一个子节点都包含指向下一个子节点的指针,从而方便叶子节点的范围遍历
B-tree索引通常意味着所有的值都是按顺序存储的,而且每一个叶子节点到根的距离相同
平衡多路查找树(B-Tree)
B-tree是为磁盘等外部存储设备设计的一种平衡查找树,因此先讲一下磁盘的基础知识.
系统从磁盘读取数据到内存是以磁盘块(block)为基本单位,位于同一个磁盘块中的数据会被一次性取出来,而不是需要什么就取什么。InnoDB存储引擎中有页(Page)的概念,页是其磁盘管理的最小单位。InnoDB 存储引擎的每个页默认大小为16KB,通过innodb_page_size将页的大小设置为4k、8k、16k,在mysql中可以通过如下命令取查看大小: show variables like 'innodb_page_size';
而系统的磁盘块的存储空间往往一个没有这么大,InnoDB在把磁盘数据读入到磁盘时会以页为基本单位,在查询数据时如果一个页中的每条数据都能有助于定位数据记录的位置,这将会减少磁盘I/O次数,提高查询效率。
B-tree结构的数据可以让系统高效的找到数据所在的磁盘块。为了描述B-tree,首先自定义一条记录为一个二元数组[key,data],key为记录的键值,对应表中的主键值,data作为一行数据中除主键外的数据。对于不同的记录,key值互不相同。一颗m阶的B-tree有如下特性:
- 每个节点最多有m个孩子
- 除了根节点和叶子节点外,其它每个节点至少有Ceil(m/2)个孩子。
- 若根节点不是叶子节点,则至少有2个孩子
- 所有叶子节点都在同一层,且不包含其它关键字信息
- 每个非终端节点包含n个关键字信息(P0,P1,…Pn, k1,…kn)
- 关键字的个数n满足:ceil(m/2)-1 <= n <= m-1
- ki(i=1,…n)为关键字,且关键字升序排序。
- Pi(i=1,…n)为指向子树根节点的指针。P(i-1)指向的子树的所有节点关键字均小于ki,但都大于k(i-1)
B-Tree中的每个节点根据实际情况可以包含大量的关键字信息和分支,如下图所示为一个3阶的B-Tree:
每个节点占用一个盘块的磁盘空间,一个节点上有两个升序排序的关键字和三个指向子树根节点的指针,指针存储的是子节点所在磁盘块的地址。两个关键词划分成的三个范围域对应三个指针指向的子树的数据的范围域。以根节点为例,关键字为17和35,P1指针指向的子树的数据范围为小于17,P2指针指向的子树的数据范围为17~35,P3指针指向的子树的数据范围为大于35。模拟查找关键字29的过程:
- 根据根节点找到磁盘块1,读入内存。【磁盘I/O操作第1次】
- 比较关键字29在区间(17,35),找到磁盘块1的指针P2。
- 根据P2指针找到磁盘块3,读入内存。【磁盘I/O操作第2次】
- 比较关键字29在区间(26,30),找到磁盘块3的指针P2。
- 根据P2指针找到磁盘块8,读入内存。【磁盘I/O操作第3次】
-
在磁盘块8中的关键字列表中找到关键字29。
分析上面过程,发现需要3次磁盘操作I/O操作,和3次内存查找操作。由于内存关键字是一个有序表结构,可以利用二分法提高查找效率。B-Tree为了保证绝对平衡他有自己的机制,比如每个节点上的关键字个数=路数(阶数-1),如下图:
可以看到添加节点后违反了原有的规则,这个时候会进行分裂。结果就会形成一根最新的树,(如果分裂过程中23 333 这个节点页不满足了会继续向上分裂):
所以建立合适的索引是很重要的 ,不宜多,当加一条数据,整棵树会进行重组
b+tree
B+Tree是在B-Tree基础上的一种优化,使其更适合实现外存储索引结构,InnoDB存储引擎就是用B+Tree实现其索引结构。
从 B-Tree 结构图中可以看到每个节点中不仅包含数据的key值,还有data值。而每一个页的存储空间是有限的,如果data数据较大时将会导致每个节点(即一个页)能存储的key的数量很小,当存储的数据量很大时同样会导致B-Tree的深度较大,增大查询时的磁盘I/O次数,进而影响查询效率。在B+Tree中,所有数据记录节点都是按照键值大小顺序存放在同一层的叶子节点上,而非叶子节点上只存储key值信息,这样可以大大加大每个节点存储的key值数量,降低B+Tree的高度。
B+Tree相对于B-Tree有几点不同:
- B+节点关键字搜索采用闭合区间
- B+非叶节点不保存数据相关信息,只保存关键字和子节点的引用
- B+关键字对应的数据保存在叶子节点中
- B+叶子节点是顺序排列的,并且相邻节点具有顺序引用的关系
将B-Tree优化,由于B+Tree的非叶子节点只存储键值信息,假设每个磁盘块能存储4个键值及指针信息,则变成B+Tree后其结构如下图所示:
通常在B+Tree上有两个头指针,一个指向根节点,另一个指向关键字最小的叶子节点,而且所有叶子节点(即数据节点)之间是一种链式环结构。因此可以对B+Tree进行两种查找运算:一种是对于主键的范围查找和分页查找,另一种是从根节点开始,进行随机查找。
在数据库中,B+Tree的高度一般都在2~4层。mysql 的InnoDB存储引擎在设计时是将根节点常驻内存的,也就是说查找某一键值的行记录时最多只需要1~3次磁盘I/O操作。数据库中的B+Tree索引可以分为聚集索引(clustered index)和辅助索引(secondary index)。上面的B+Tree示例图在数据库中的实现即为聚集索引,聚集索引的B+Tree中的叶子节点存放的是整张表的行记录数据。辅助索引与聚集索引的区别在于辅助索引的叶子节点并不包含行记录的全部数据,而是存储相应行数据的聚集索引键,即主键。当通过辅助索引来查询数据时,InnoDB存储引擎会遍历辅助索引找到主键,然后再通过主键在聚集索引中找到完整的行记录数据。
B+Tree在MYSQL中采用的是 左闭合区间,MYSQL推崇使用ID作为索引,由于ID是自增的数字类型,只会增大,所以采用向右拓展的一个方式,从根节点进行比对,由于枝节点不保存数据,无所谓命不命中,都要继续走到叶子节点才能加载数据。
B+Tree在两大引擎中如何体现:
那么在 MYSQL中比较主流的两大引擎是:Myisam 跟 innoDB,存储引擎是建立在表上面的,在建立表的时候可以指定所需要的搜索引擎。例如下列的创建语句中就指定了搜索引擎为:ENGINE=InnoDB,不指定就使用默认的InnoDB
CREATE TABLE 'USER' (
'id' int(11) not NUll,
'name' varchr(50) default NUll
,
PRIMARY KEY ('id')
) ENGINE=innodb DEFAULT CHARSET=utf8;
B+Tree 在 Myisam 中的体现:
在创建好表结构并且指定搜索引擎为 Myisam之后,会在数据目录生成3个文件,分别是table_name.frm(表结构文件),table_name.MYD(数据保存文件),table_name.MYI(索引保存文件)。
两个文件分别保存了数据及索引,由于B+Tree中只有叶子节点保存数据区,在Myisam中,数据区中保存的是数据的引用地址,就比如说ID为101的数据信息所保存到物理磁盘地址为 0x123456,在索引中的节点数据去中所保存的就是这个磁盘地址指针。当扫描到这个指针位置,就可以通过这个磁盘指针讲数据加载出来
在Myisam中B+Tree的实现中比如现在不用ID作为索引了,要用name,那么他的一个展现形式有事怎么样的呢?其实他与ID作为索引是一样的,也是保存他指定的磁盘位置指针,他们是平级的。如下图:
B+Tree 在 InnoDB 中的体现:
在创建好表结构并且指定搜索引擎为Innodb之后,会在数据目录生成2个文件,分别是table_name.frm(表结构文件),table_name.idb(数据与索引保存文件)。
在 InnoDB中,因为设计之初就是认为主键是非常重要的。是以主键为索引来组织数据的存储,当我们没有显示的建立主键索引的时候,搜索引擎会隐式的为我们建立一个主键索引以组织数据存储。数据库表行中数据的物理顺序与键值的逻辑(索引)顺序相同,InnoDB就是以聚集索引来组织数据的存储的,在叶子节点上,保存了数据的所有信息。如果这个时候建立了name字段的索引:
会产生一个辅助索引,即name字段的索引,而此刻叶子节点上所保存的数据为 聚集索引(ID索引)的关键字的值,基于辅助索引找到ID索引的值,再通过ID索引区获取最终的数据。这个做法的好处是在于产生数据迁移的时候只要ID没发生变法,那么辅助索引不需要重新生成,不这么做的话,如果存储的是磁盘地址的话,在数据迁移后所有辅助索引都需要重新生成。
索引知识:
找出离散性最好的列:
越大离散型越好 count(distinct col):count(col) 理解为差异性。结论:离散性越高选择性就越好
最左匹配原则:
- 经常用的列优先 【最左匹配原则】。
- 选择性(离散度)高的列优先【离散度高原则】。
- 宽度小的列优先【最少空间原则】。
覆盖索引:
果查询列可通过索引节点中的关键字直接返回,则该索引称之为覆盖索引。覆盖索引可减少数据库IO,将随机IO变为顺序IO,可提高查询性能。比说所建立了一个联合索引 reate index idx_name_phoneNum on users(name,phoneNum);而此刻有sql select name phoneNum from 。。。。这个就是覆盖索引
总结及验证:
索引列的数据长度能少则少。索引一定不是越多越好,越全越好,一定是建合适的。查询条件上有计算函数无法命中索引。
匹配列前缀:like 9999%(最原则上按照左匹配上来说是可以的,但是不一定能用到索引,当离散性太差的时候就不行),like %9999%(不行)、like %9999(不行)用不到索引;
Where 条件中 not in 和 <>操作无法使用索引;匹配范围值,order by 也可用到索引;多用指定列查询,只返回自己想到的数据列,少用select *;
联合索引中如果不是按照索引最左列开始查找,无法使用索引;
联合索引中精确匹配最左前列并范围匹配另外一列可以用到索引;比如联合索引【name,phoneNum】,当SQL为:select .....where name='1' and phoneNum>xxxxxxx.
联合索引中如果查询中有某个列的范围(大于小于)查询,则其右边的所有列都无法使用索引;