【mysql】Mysql性能优化基础之索引数据结构类型Btree和Hash的深入探讨

我们大家都是知道,如果数据库遇到查询性能问题,就会去检查是否命中了索引,但大家是否真正了解过mysql底层数据及索引结构是怎么存储的吗?下面就来逐步谈到一下;首先我们来看看一些数据结构,分别来比较一下为什么mysql开发人员选择B+tree,而不选择其他数据结构存储;

各个数据结构对比

二叉查找树(Binary Search Tree)

二叉查找树(Binary Search Tree),(又:二叉搜索树,二叉排序树)它或者是一棵空树,或者是具有下列性质的二叉树,如果我们数据库索引结构采用二叉树结构存储,如图所示,会导致我们的树层级会越来越高,当然也起不到索引的效果,数据量越多树就越高;
特点:
a. 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
b. 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
c. 它的左、右子树也分别为二叉排序树。
二叉查找树数据结构
假如我们这里有1000W的排序数据存入,如果才有这种查找方式,则最大有1000W次查询,显然是不可取的,相当于全表扫描了!那么我们如何减少树高呢?再看看下一个数据结构;

红黑树(Red/Black Tree)

平衡二叉树,通过对任何一条从根到叶子的简单路径上各个节点的颜色进行约束,确保没有一条路径会比其他路径长2倍,因而是近似平衡的。所以相对于严格要求平衡的AVL树来说,它的旋转保持平衡次数较少。用于搜索时,插入删除次数多的情况下我们就用红黑树来取代AVL。虽然红黑树相比传统的二叉树减少了层级;但在1000W树量基础上 2^n =1000W,
n也约等于31,及如果mysql索引采用该数据结构,最多需要31次IO开销才能找到需要的数据,也非常耗性能;那么我们思考一下如何进一步减少树高呢?我想大家已经想到了,就是在单个树节点上多存储一些数据,做到水平扩展;树高不就减少了吗!接下来就要介绍另外一种树形结构BTree;
红黑树数据结构

B-Tree

B-tree(多路搜索树,并不是二叉的)是一种常见的数据结构。使用B-tree结构可以显著减少定位记录时所经历的中间过程,从而加快存取速度。按照翻译,B 通常认为是Balance的简称。这个数据结构一般用于数据库的索引,综合效率较高。
B-tree中,每个结点包含:
1、本结点所含关键字的个数;
2、指向父结点的指针;
3、关键字;
4、指向子结点的指针;
对于一棵m阶B-tree,每个结点至多可以拥有m个子结点。各结点的关键字和可以拥有的子结点数都有限制,规定m阶B-tree中,根结点至少有2个子结点,除非根结点为叶子节点,相应的,根结点中关键字的个数为1m-1;非根结点至少有[m/2]([],向上取整)个子结点,相应的,关键字个数为[m/2]-1m-1。

B-tree有以下特性:
1、关键字集合分布在整棵树中;
2、任何一个关键字出现且只出现在一个结点中;
3、搜索有可能在非叶子结点结束;
4、其搜索性能等价于在关键字全集内做一次二分查找;
5、自动层次控制;
由于限制了除根结点以外的非叶子结点,至少含有M/2个儿子,确保了结点的至少利用率,其最低搜索性能为:
其中,M为设定的非叶子结点最多子树个数,N为关键字总数;
所以B-树的性能总是等价于二分查找(与M值无关),也就没有B树平衡的问题;
由于M/2的限制,在插入结点时,如果结点已满,需要将结点分裂为两个各占M/2的结点;删除结点时,需将两个不足M/2的兄弟结点合并。B-tree数据结构图
这样得数据结构又比红黑树更好,因为它们分支多层数少,都知道磁盘IO是非常耗时的,而像大量数据存储在磁盘中所以我们要有效的减少磁盘IO次数避免磁盘频繁的查找。这样其实还是有问题,每个结点都存储有数据,存储空间也是很大不说,如果存在频繁插入数据,会导致数据结构变化导致都排序开销,当然我们mysql存储引擎更加优化了一下,用的是B+tree,什么又叫B+tree呢?接下来继续介绍;

B+Tree

B+树(B+Tree)是B树(B-tree)的变种树,有n棵子树的节点中含有n个关键字,每个关键字不保存数据,只用来索引,数据都保存在叶子节点。是为文件系统而生的。这样就会大大减少磁盘IO的读写操作;结构图如下,当然mysql开发者们在B+Tree存储索引及数据方面做了些优化,不同的数据存储引擎存储方式不一样;
B+Tree数据结构图我们都知道mysql有两个常用都存储引擎(MyISAM和Innodb),细心的朋友可能发现,mysql库中的表,他们的物理存储文件会不一样;如下图所示:
查看mysql库表结构
下面来解释一下两张表不同文件代表的意思,首先是user表,因为user表使用的存储引擎为MyISAM

  1. user.frm-----------这个是存储表结构的;
  2. user.MYD------------是存储表行列数据的;
  3. user.MYI-------------是存储索引的,当我们查找到数据索引后会先加载这个,然后在索引文件中找到对应的数据存储文件指针,这里即指向user.MYD数据文件;

而time_zone_name表,使用的存储引擎是Innodb

  1. time_zone_name.frm ------------这个是存储表结构的,通user.frm 相同;
  2. time_zone_name.idb------------这个是存储索引和数据的集合体,数据和索引在一起存储,这样的索引常称为聚集索引,当然也有非聚集索引。mysql有且仅一个聚集索引(即一个主键,如果你没有创建组建,系统会自动创建一个主键),其作用是一个排好序的数据,加快检索速度;为什么只能是一个聚集索引呢?原因也不难理解,排序只能按照一种规则进去排序下去;

存储引擎MyISAM与Innodb区别对比

MyISAM存储引擎

  1. 不支持事务,但是整个操作是原子性的(事务具备四种特性:原子性、一致性、隔离性、持久性)

  2. 不支持外键,支持表锁,每次所住的是整张表
    MyISAM的表锁有读锁和写锁(两个锁都是表级别):
    表共享读锁和表独占写锁。在对MyISAM表进行读操作时,不会阻塞其他用户对同一张表的读请求,但是会阻塞其他用户对表的写请求;对其进行写操作时会阻塞对同一表读操作和写操作;
    MyISAM存储引擎的读锁和写锁是互斥的,读写操作是串行的。那么,一个进程请求某个MyISAM表的读锁,同时另一个进程也请求同一表的写锁,MySQL如何处理呢?答案是写进程先获得锁。不仅如此,即使读请求先到锁等待队列,写请求后到,写锁也会插到读锁请求之前!这是因为MySQL认为写请求一般比读请求要重要。这也正是MyISAM表不太适合于有大量更新操作和查询操作应用的原因,因为,大量的更新操作会造成查询操作很难获得读锁,从而可能永远阻塞。这种情况有时可能会变得非常糟糕!

  3. 一个MyISAM表有三个文件:索引文件,表结构文件,数据文件

  4. 存储表的总行数,执行select count() from table时只要简单的读出保存好的行数即可,(myisam存储引擎的表,count()速度快的也仅仅是不带where条件的count。这个想想容易理解的,因为你带了where限制条件,原来所以中缓存的表总数能够直接返回用吗?不能用。这个查询引擎也是需要根据where条件去表中扫描数据,进行统计返回的。)

  5. 采用非聚集索引,索引文件的数据域存储指向数据文件的指针。辅索引与主索引基本一致,但是辅索引不用保证唯一性。

  6. 支持全文索引和空间索引

  7. 对于AUTO_INCREMENT类型的字段,在MyISAM表中,可以和其他字段一起建立联合索引。
    MyISAM的主索引图:索引文件的每个数据域存储指向数据文件的指针(每个索引指向了数据地址)
    MyISAM的主索引图
    MyISAM的辅索引:索引文件的每个数据域存储指向数据文件的指针(每个索引指向了数据地址),辐索引不用保证唯一性
    在这里插入图片描述

Innodb存储引擎:

  1. 支持事务,支持事务的四种隔离级别;是一种具有事务(commit)、回滚(rollback)和崩溃修复能力(crash recovery capabilities)的事务安全(transaction-safe (ACID compliant))型表。

  2. 支持行锁和外键约束,因此可以支持写并发

  3. 不存储总行数;也就是说,执行select count() from table时,InnoDB要扫描一遍整个表来计算有多少行。注意的是,当count()语句包含 where条件时,两种表的操作是一样的。

  4. 对于AUTO_INCREMENT类型的字段,InnoDB中必须包含只有该字段的索引

  5. DELETE FROM table时,InnoDB不会重新建立表,而是一行一行的删除

  6. 一个Innodb表存储在一个文件内(共享表空间,表大小不受操作系统的限制),也可能为多个(设置为独立表空间,表大小受操作系统限制,大小为2G),受操作系统文件大小的限制

  7. 主键索引采用聚集索引(索引的数据与存储数据文件是本身),辅索引的数据域存储主键的值;因此从辅索引查找数据,需要先通过辅索引找到主键值,再访问主键索引;最好使用自增主键,防止插入数据时,为维持B+树结构,文件的大调整。
    Innodb的主索引图:(索引位置上存储的直接是数据本身)
    在这里插入图片描述
    Innodb的辅索引图:在这里插入图片描述

总结大图:

存储引擎MyISAM与Innodb对比图

Mysql的另外一个索引数据结构Hash

Mysql为何默认不用hash索引而用BTree索引的几点原因?

Hash索引不像B-Tree 索引需要从根节点到枝节点,最后才能访问到页节点这样多次的IO访问,所以 Hash 索引的查询效率要远高于 B-Tree 索引,它会将计算出的Hash值和对对应的行指针信息记录在Hash表中。但是虽然Hash效率很高但是同样也有很多的弊端存在和限制存在。

(1)Hash 索引仅仅能满足"=",“IN"和”<=>"查询,不能使用范围查询。

(2)Hash 索引无法被用来避免数据的排序操作。

(3)Hash 索引不能利用部分索引键(组合索引)查询。

(4)Hash 索引在任何时候都不能避免表扫描。

(5)Hash 索引遇到大量Hash值相等的情况后性能并不一定就会比B-Tree索引高。

因为Hash 索引比较的是进行 Hash 运算之后的 Hash 值,所以它只能用于等值的过滤,不能用于基于范围的过滤。经过相应的 Hash 算法处理之后的 Hash 值的大小关系,并不能保证和Hash运算前完全一样,数据库自然也无法利用索引的数据来避免任何排序运算。

Hash 索引在计算 Hash 值的时候是组合索引键合并后再一起计算 Hash 值,而不是单独计算 Hash 值,所以通过组合索引的前面一个或几个索引键进行查询的时候,Hash 索引也无法被利用。而我们常用的业务中不可能没有范围查询,所以Hash索引就不太常用咯;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章