性能调优-MySQL索引数据结构详解与索引优化

转载请注明出处!!!掘金:鸟不拉屎html

本篇文章主要学习了MySQL的索引的数据结构的认识,作一个大概的了解便可。node

1、索引

在关系数据库中,索引是一种单独的、物理的对数据库表中一列或多列的值进行排序的一种存储数据结构,它是某个表中一列或若干列值的集合和相应的指向表中物理标识这些值的数据页的逻辑指针清单。索引的做用至关于图书的目录,能够根据目录中的页码快速查找到所需的内容。mysql

在MySQL中,存储引擎用相似的方法使用索引,先在索引中找到对应值,而后根据匹配的索引记录找到对应的行。程序员

首先说明下MySQL的索引主要是基于Hash表或者B+树。算法

2、索引数据结构

了解索引就须要从索引常见的数据结构开始了解学习,这里有集中常见的的索引数据结构。sql

二叉树(Binary Trees)

二叉树是每一个节点最多只有两个分支(即不存在分支度大于2的节点)的树结构。一般被称之为“左子树”和“右子树”数据库

二叉树示例图

左子树<父节点<=右子树数组

二叉树的第i层至多有有2^(i-1)个节点,缓存

深度为K的二叉树至多总共有个2^k-1节点(定义根节点所在深度 k0=0),而总计拥有节点数符合的,称为“满二叉树”;数据结构

二叉树一般做为数据结构应用,典型用法是对节点定义一个标记函数,将一些值与每一个节点相关系。这样标记的二叉树就能够实现二叉搜索树二叉堆,并应用于高效率的搜索和排序。

同时学习数据结构,这里还推荐Data Structure Visualizations进行学习,能够很是直观的看到数据结构容许的过程,一步一步的怎么走的均可以很清晰看获得。

找到其中的Binary Search Trees二叉树

1566357073576.png

能够直观的看到二叉树的数据插入过程,以下:

二叉树插入过程

能够看到二叉树不适合用做看成索引的,数据量庞大的话,二叉树的层数会很大,查找效率当然也很慢了。

推荐阅读:维基百科-二叉树

红黑树(Red-Black Trees)

是一种自平衡二叉查找树,典型用途是实现关联数组。

红黑树的结构复杂,但它的操做有着良好的最坏状况运行时间,而且在实践中高效:它能够在O(log n)时间内完成查找,插入和删除,这里的n是树中元素的数目。

红黑树遵行如下原则:

  1. 节点是红色或黑色。
  2. 根是黑色。
  3. 全部叶子都是黑色(叶子是NIL节点)。
  4. 每一个红色节点必须有两个黑色的子节点。(从每一个叶子到根的全部路径上不能有两个连续的红色节点。)
  5. 从任一节点到其每一个叶子的全部简单路径都包含相同数目的黑色节点。

下面是一个具体的红黑树的图例:

简单红黑树示例图

这些约束确保了红黑树的关键特性:从根到叶子的最长的可能路径很少于最短的可能路径的两倍长。结果是这个树大体上是平衡的。由于操做好比插入、删除和查找某个值的最坏状况时间都要求与树的高度成比例,这个在高度上的理论上限容许红黑树在最坏状况下都是高效的,而不一样于普通的二叉查找树

要知道为何这些性质确保了这个结果,注意到性质4致使了路径不能有两个毗连的红色节点就足够了。最短的可能路径都是黑色节点,最长的可能路径有交替的红色和黑色节点。由于根据性质5全部最长的路径都有相同数目的黑色节点,这就代表了没有路径能多于任何其余路径的两倍长。

一样在Data Structure Visualizations中选择Red-Black Trees红黑树进行插入操做能够直观的看到红黑树的插入过程

红黑树插入过程

一样红黑树也不适用于MySQL的索引,数据量庞大以后,数层也会变大。

推荐阅读:

维基百科-红黑树

程序员小灰-红黑树

其余结构的问题

因为没法装入内存,则必然依赖磁盘(或SSD)存储。而内存的读写速度是磁盘的成千上万倍(与具体实现有关),所以,核心问题是“如何减小磁盘读写次数”。

首先不考虑页表机制,假设每次读、写都直接穿透到磁盘,那么:

  • 线性结构:读/写平均O(n)次
  • 二叉搜索树(BST):读/写平均O(log2(n))次;若是树不平衡,则最差读/写O(n)次
  • 自平衡二叉搜索树(AVL):在BST的基础上加入了自平衡算法,读/写最大O(log2(n))次
  • 红黑树(RBT):另外一种自平衡的查找树,读/写最大O(log2(n))次

BSTAVLRBT很好的将读写次数从O(n)优化到O(log2(n));其中,AVLRBT都比BST多了自平衡的功能,将读写次数降到最大O(log2(n))。

假设使用自增主键,则主键自己是有序的,树结构的读写次数可以优化到树高,树高越低读写次数越少;自平衡保证了树结构的稳定。若是想进一步优化,能够引入B树B+树

B树(B-Trees)

又称:多路平衡查找树。大多数存储引擎都支持B树索引。b树一般意味着全部的值都是按顺序存储的,而且每个叶子节点到根的距离相同。B树索引可以加快访问数据的速度,由于存储引擎再也不须要进行全表扫描来获取数据。下图就是一颗简单的B树。

在B树中,内部(非叶子)节点能够拥有可变数量的子节点(数量范围预先定义好)。当数据被插入或从一个节点中移除,它的子节点数量发生变化。为了维持在预先设定的数量范围内,内部节点可能会被合并或者分离。

以下图所示:

B数结构图

  • 叶节点具备相同的深度,叶节点的指针为空
  • 全部索引元素不重复
  • 节点中的数据索引从左到右递增排列
  • 不管中间节点仍是叶子节点都带有卫星数据data(索引元素所指向的数据记录)

B树插入过程

只演示了插入的过程,其中能够经过delete、find执行删除和查找操做。直观的感觉到B树的执行过程。

每一个节点存储了多个Key和子树,子树与Key按顺序排列。

同二叉搜索树相似,每一个节点存储了多个key和子树,子树与key按顺序排列。

页表的目录是扩展外存+加速磁盘读写,一个页(Page)一般4K(等于磁盘数据块block的大小,见inode与block的分析),操做系统每次以页为单位将内容从磁盘加载到内存(以摊分寻道成本),修改页后,再择期将该页写回磁盘。考虑到页表的良好性质,可使每一个节点的大小约等于一个页(使m很是大),这每次加载的一个页就能完整覆盖一个节点,以便选择下一层子树;对子树同理。对于页表来讲,AVL(或RBT)至关于1个key+2个子树的B树,因为逻辑上相邻的节点,物理上一般不相邻,所以,读入一个4k页,页面内绝大部分空间都将是无效数据。

假设key、子树节点指针均占用4B,则B树节点最大m * (4 + 4) = 8m B;页面大小4KB。则m = 4 * 1024 / 8m = 512,一个512叉的B树,1000w的数据,深度最大 log(512/2)(10^7) = 3.02 ~= 4。对比二叉树如AVL的深度为log(2)(10^7) = 23.25 ~= 24,相差了5倍以上。震惊!B树索引深度居然如此!

那为何B数这么厉害了,还有B+树的出现呢,必然是解决B树存在的问题

一、为定位行数

二、没法处理范围查询

问题1:为定位行数

数据表的记录有多个字段,仅仅定位到主键是不够的,还须要定位到数据行。有3个方案解决:

  1. 直接将key对应的数据行(可能对应多行)存储子节点中。
  2. 数据行单独存储;节点中增长一个字段,定位key对应数据行的位置。
  3. 修改key与子树的判断逻辑,使子树大于等于上一key小于下一key,最终全部访问都将落于叶子节点;叶子节点中直接存储数据行或数据行的位置。

方案1直接pass,存储数据行将减小页面中的子树个数,m减少树高增大。

方案2的节点中增长了一个字段,假设是4B的指针,则新的m = 4 * 1024 / 12m = 341.33 ~= 341,深度最大 log(341/2)(10^7) = 3.14 ~= 4

方案3的节点m与深度不变,但时间复杂度变为稳定的O(logm(n))。

方案3能够考虑。

问题2:没法处理范围查询

实际业务中,范围查询的频率很是高,B树只能定位到一个索引位置(可能对应多行),很难处理范围查询。改动较小的是2个方案:

  1. 不改动;查询的时候先查到左界,再查到右界,而后DFS(或BFS)遍历左界、右界之间的节点。
  2. 在“问题1-方案3”的基础上,因为全部数据行都存储在叶子节点,B树的叶子节点自己也是有序的,能够增长一个指针,指向当前叶子节点按主键顺序的下一叶子节点;查询时先查到左界,再查到右界,而后从左界到有界线性遍历。

乍一看感受方案1比方案2好——时间复杂度和常数项都同样,方案1还不须要改动。可是别忘了局部性原理,无论节点中存储的是数据行仍是数据行位置,方案2的好处在于,依然能够利用页表和缓存预读下一节点的信息。而方案1则面临节点逻辑相邻、物理分离的缺点。 推荐阅读:

维基百科-B树

程序员小灰-B树

B+树(B+Trees)

主要变更如上所述:

  • 修改key与子树的组织逻辑,将索引访问都落到叶子节点
  • 按顺序将叶子节点串起来(方便范围查询)

回顾上一个B树,一个m阶的B树具备以下几个特征:

1.根结点至少有两个子女。

2.每一个中间节点都包含k-1个元素和k个孩子,其中 m/2 <= k <= m

3.每个叶子节点都包含k-1个元素,其中 m/2 <= k <= m

4.全部的叶子结点都位于同一层。

5.每一个节点中的元素从小到大排列,节点当中k-1个元素正好是k个孩子包含的元素的值域分划。

一个m阶的B+树具备以下几个特征:

1.有k个子树的中间节点包含有k个元素(B树中是k-1个元素),每一个元素不保存数据,只用来索引,全部数据都保存在叶子节点。

2.全部的叶子结点包含了所有元素的信息,及指向含这些元素记录的指针,且叶子结点自己依关键字的大小自小而大顺序连接。

3.全部的中间节点元素都同时存在于子节点,在子节点元素中是最大(或最小)元素。

B+树特性总结

B+树是B树的升级版,其有以下特性

  • 非叶子节点不存储data,只存储索引(冗余),能够放更多的索引
  • 叶子节点包含全部索引字段
  • 叶子节点用指针链接,提升区间访问的性能
  • 只有叶子节点带有卫星数据data(索引元素所指向的数据记录)

1566368976038.png

一样在Data Structure Visualizations中选择B+ TreesB+树进行插入操做能够直观的看到插入过程

B+树插入过程

在动图中能够看出,B+树的每个叶子节点都有一个指针指向下一个节点,把全部的叶子节点串在一块儿。索引数据都存储在叶子节点中。

B+树相比于B树,有什么优点呢:

1.单一节点存储更多的元素,使得查询的IO次数更少。

2.全部查询都要查找到叶子节点,查询性能稳定。

3.全部叶子节点造成有序链表,便于范围查询。

总结,B+树相比B树的优点有三:1.IO次数更少;2.查询性能稳定;3.范围查询简便。

推荐阅读:

维基百科-B+树

程序员小灰-B+树

Hash索引

hash索引基于hash表实现,Hash 索引是将索引键经过 Hash 运算以后,将 Hash运算结果的 Hash 值和所对应的行指针信息存放于一个 Hash 表中。只有精准匹配索引全部列的查询才有效。索引的检索能够一次定位,不像B-Tree索引须要从根节点出发到目标节点。虽然Hash索引很快,远高于B-tree索引,可是也有其弊端。

  1. Hash索引仅仅能知足'=','IN','<=>'查询,也就是等值查询,不能使用范围查询。很受限
    1. 因为 Hash 索引比较的是进行 Hash 运算以后的 Hash 值,因此它只能用于等值的过滤,不能用于基于范围的过滤,由于通过相应的 Hash 算法处理以后的 Hash 值的大小关系,并不能保证和Hash运算前彻底同样。
  2. 因为Hash索引是经过hash表实现,其自己是没有排序的。
    1. 因为 Hash 索引中存放的是通过 Hash 计算以后的 Hash 值,并且Hash值的大小关系并不必定和 Hash 运算前的键值彻底同样,因此数据库没法利用索引的数据来避免任何排序运算;
  3. Hash索引不能利用部分索引键查询
    1. 对于组合索引,Hash索引在计算hash值的时候是组合索引键合并后再一块儿计算hash值,而不是单独计算hash值,因此经过组合索引的前面一个或几个索引键进行查询的时候,Hash 索引也没法被利用。
  4. Hash 索引在任什么时候候都不能避免表扫描
    1. 前面已经知道,Hash 索引是将索引键经过 Hash 运算以后,将 Hash运算结果的 Hash 值和所对应的行指针信息存放于一个 Hash 表中,因为不一样索引键存在相同 Hash 值,因此即便取知足某个 Hash 键值的数据的记录条数,也没法从 Hash 索引中直接完成查询,仍是要经过访问表中的实际数据进行相应的比较,并获得相应的结果。
  5. Hash 索引遇到大量Hash值相等的状况后性能并不必定就会比B-Tree索引高。
    1. 对于选择性比较低的索引键,若是建立 Hash 索引,那么将会存在大量记录指针信息存于同一个 Hash 值相关联。这样要定位某一条记录时就会很是麻烦,会浪费屡次表数据的访问,而形成总体性能低下。

3、MySQL数据库引擎

经过navicat工具查看表设计选项中,从引擎中能够看到MySQL又这么多引擎。具体细分到每一个表,不一样的表引擎能够不同。

MySQL数据库引擎

MyISAM

新建一张表t_test_myisam,引擎使用MyISAM,查看原文件能够看到有3个文件

MyISAM索引结构原文件

能够看到索引和数据是分开的,其中索引文件仅仅保存数据记录的地址,故属于非聚簇索引

主键索引(Primary Index)

MyISAM引擎使用B+Tree做为索引结构,叶节点的data存放的是数据记录的地址。以下图是MyISAM主键索引的原理图。

MyISAM主键索引

其中Col1为主键,能够看出看出MyISAM的索引文件仅保存数据记录的地址。

辅助索引(Secondary Index)

在Col2上创建一个辅助索引,以下图辅助索引原理图。

MyISAM辅助索引

能够看到与主键索引没有任何区别,只不过主键索引的key是惟一的,而辅助索引的key能够重复。

MyISAM中索引检索的算法为首先按照B+Tree搜索算法搜索索引,若是指定的Key存在,则取出其data域的值,而后以data域的值为地址,读取相应数据记录。

InnoDB

新建一张表t_test_innodb,引擎使用InnoDB,查看原文件能够看到有2个文件

InnoDB索引结构原文件

主键索引(Primary Index)

InnoDB的索引和数据在一个文件当中。

按照B+Tree组织的一个索引结构。

叶节点保存了完整的数据记录和索引。这种索引就叫作聚簇索引

索引的Key是数据的主键,所以InnoDB表数据文件自己就是主索引。

以下图:

InnoDB主键索引

能够看到叶节点包含了完整的数据记录。

由于InnoDB的数据文件自己要按照主键汇集,因此InnoDB要求必须有主键。若是没有显式指定,则MySQL系统会自动选择一个能够惟一标识数据记录的列做为主键,若是不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段rowid做为主键,这个字段长度为6个字节,类型为长整形。

辅助索引(Secondary Index)

辅助索引,将途中的第二行name,做为索引如图

InnoDB辅助索引

聚簇索引这种实现方式使得按照主键的搜索十分高效,可是首先检索辅助索引得到主键,而后用主键到主索引中检索得到记录

因为InnoDB索引的实现特性,推荐使用整形的自增主键。

有三点好处:

  1. 自增key通常为int等整数型,key比较紧凑,这样m能够很是大,并且索引占用空间小。最极端的例子,若是使用50B的varchar(包括长度),那么m = 4 * 1024 / 54m = 75.85 ~= 76,深度最大log(76/2)(10^7) = 4.43 ~= 5,再加上cache缺失、字符串比较的成本,时间成本增长较大。同时,key由4B增加到50B,整棵索引树的空间占用增加也是极为恐怖的(若是二级索引使用主键定位数据行,则空间增加更加严重)。
  2. MySQL索引底层的数据比较都是整数型比较,若是主键时字符串类型的,里面还有英文,还得转换ASCII码进行比较。因此不建议使用uuid做为主键。
  3. 自增的主键使得数据行的插入好比落到索引数的最右侧,发生节点分裂的频率较低。B+Tree实际操做插入过程。若是不是非单调主键,插入过程很大程度会发生节点重排,不利于索引优化的初衷。

InnoDB索引和MyISAM索引的区别

一是主索引的区别:InnoDB的数据文件自己就是索引文件。而MyISAM的索引和数据是分开的。

二是辅助索引的区别:InnoDB的辅助索引data域存储相应记录主键的值而不是地址。而MyISAM的辅助索引和主索引没有多大区别。

4、覆盖索引

InnoDB存储引擎支持覆盖索引,即从辅助索引中就能够获得查询的记录,不须要查询聚簇索引中的记录了。能够减小大量的IO操做。

若是要查询辅助索引中不含有的字段,得先遍历辅助索引,再遍历汇集索引,而若是要查询的字段值在辅助索引上就有,就不用再查汇集索引了,这显然会减小IO操做。

5、联合索引

两个或以上的列上的索引。以下图联合索引的原理图:

联合索引原理图

上图中的联合索引有三个,从上到下,严格按照排序。

6、优化建议

最左前缀匹配

索引能够简单如一个列(a),也能够复杂如多个列(a, b, c, d),即联合索引。若是是联合索引,那么key也由多个列组成,同时,索引只能用于查找key是否存在(相等),遇到范围查询(>、<、between、like左匹配)等就不能进一步匹配了,后续退化为线性查找。所以,列的排列顺序决定了可命中索引的列数。

若有索引(a, b, c, d),查询条件a = 1 and b = 2 and c > 3 and d = 4,则会在每一个节点依次命中a、b、c,没法命中d。也就是最左前缀匹配原则。

=、in自动优化顺序

不须要考虑=、in等的顺序,mysql会自动优化这些条件的顺序,以匹配尽量多的索引列。

若有索引(a, b, c, d),查询条件c > 3 and b = 2 and a = 1 and d < 4a = 1 and c > 3 and b = 2 and d < 4等顺序都是能够的,MySQL会自动优化为a = 1 and b = 2 and c > 3 and d < 4,依次命中a、b、c。

索引列不能参与计算

有索引列参与计算的查询条件对索引不友好(甚至没法使用索引),如from_unixtime(create_time) = '2014-05-29'

缘由很简单,如何在节点中查找到对应key?若是线性扫描,则每次都须要从新计算,成本过高;若是二分查找,则须要针对from_unixtime方法肯定大小关系。

所以,索引列不能参与计算。上述from_unixtime(create_time) = '2014-05-29'语句应该写成create_time = unix_timestamp('2014-05-29')

能扩展就不要新建索引

若是已有索引(a),想创建索引(a, b),尽可能选择修改索引(a)为索引(a, b)。

新建索引的成本很容易理解。而基于索引(a)修改成索引(a, b)的话,MySQL能够直接在索引a的B+树上,通过分裂、合并等修改成索引(a, b)。

不须要创建前缀有包含关系的索引

若是已有索引(a, b),则不须要再创建索引(a),可是若是有必要,则仍然需考虑创建索引(b)。

7、索引的优缺点

优势

  1. 经过建立惟一索引,能够保证数据库每一行数据的惟一性
  2. 能够大大提升查询速度
  3. 能够加速表与表的链接
  4. 能够显著的减小查询中分组和排序的时间。

缺点

  1. 建立索引和维护索引须要时间,并且数据量越大时间越长
  2. 建立索引须要占据磁盘的空间,若是有大量的索引,可能比数据文件更快达到最大文件尺寸
  3. 当对表中的数据进行增长,修改,删除的时候,索引也要同时进行维护,下降了数据的维护速度
相关文章
相关标签/搜索