您的位置:首页 > 理论基础 > 数据结构算法

深入理解Mysql——索引底层数据结构与算法

2020-05-21 20:56 99 查看

这里写目录标题

  • 索引是怎么支撑千万级表的快速查找
  • 存储引擎
  • 联合索引底层数据结构
  • 索引数据结构

    二叉搜索树

    对于二叉搜索树来说,他的左子树的所有节点都要小于根节点,他的右子树的所有节点都要大于根节点。但是如果输入的数据顺序本身就是有序的(比如数据节点是从小到大排序好的,就会出现下图的问题)。

    从时间复杂度分析:之所以采用二叉树的形式存储数据,就是为了将链表的时间复杂度O(n),降低为O(logn),如果出现下图的情况,则与普通链表毫无区别。

    从系统I/O分析:每取一次数(从硬盘取出,放入内存),就要有一次I/O操作,而I/O操作是十分消耗系统资源的,如果要找到数字6,就要经过6次I/O操作。这在数据很少的情况下或许可以接受,但是如果是企业级开发,往往面临百万级,千万级的数据,如果生成的二叉树是这样,那么系统就没有效率可言了。

    红黑树

    由二叉搜索树进化而来,红黑树是比二叉搜索树效率更高的数据结构。红黑树脱胎于平衡二叉树,所以就有:
    先按照生成二叉搜索树的方法构造二叉树,直至二叉树变得不平衡,即出现这样的节点:左子树与右子树的高度差大于1。这时我们要调整树的结构。

    同样的数据,输出的红黑树如上图所示。

    那么如果还是查找6,就要从根节点开始,经过3次I/O。

    虽然明显比二叉搜索树的效率要高,但还是同样的问题,如果面对百万级数据的企业开发,他的效率仍然不会很好。同样也是因为过多的I/O导致效率的下降。

    Hash表

    hash算法计算出地址(定位到磁盘文件对应的指针)。
    为什么一般不用呢?不是由于hash碰撞(MySQL对hash碰撞做了很多处理,出现hash碰撞的概率很低)。
    不用的原因,是因为hash表没法执行范围查找(hash算法算出来的只是一个地址,如果遇到范围查找,没法处理)。
    如果业务只存在等值查询,hash表的效率更高。

    B-Tree

    话不多说,直接上图
    同样是找到6,这里只需要两次I/O
    这里Max. Degree = 4
    B-Tree有以下特性:

    • 叶节点具有相同的深度,叶节点的指针为空
    • 所有索引元素不重复
    • 节点中的数据索引从左到右递增排列


    在MySQL中,存储节点的大小是由要求的,默认值为16K。如果使用B-Tree,那么每个节点中会存储多个数据(这些数据一般都是由键值对构成,Key是编号,比如下图的15,Value是实际存储的值,如下图的data。这样一来每个节点实际可存储的键值对就非常有限)如果遇到data比较大的情况,那么每个节点可存储的数据就有限,同样会造成树的深度很大(面对百万级数据的情况)。

    B+Tree

    特点:

    • 非叶子节点不存储data,只存储索引(冗余),可以放更多的索引
    • 叶子节点包含所有索引字段
    • 叶子节点用指针连接,提高区间访问的性能

    索引是怎么支撑千万级表的快速查找

    首先一点,相比于B-Tree,B+Tree除了叶子节点之外,中间层的节点都不会存储data。
    那么我们来估算一下,一般来说在MySQL默认设置的情况下,节点大小为16KB,Key(绿色编号:15,56)占用的大小大约是8B,两个绿色块之间的白色区域记录的是指针(指向下层节点的地址),所占大小大于是6B,那么(除叶子节点外)每个节点可以含有的 编号-指针 组合的数量是16KB/(8+6)B=1170个。如果连同叶子节点(假设每个data大小是1KB,那么一个叶子节点大约可以存16个数据),3层B+Tree可以生成(1170117016=21,902,400个节点)大约2.1千万个数据的索引。也就是实现千万级的索引查找,只要进行3次I/O就可以。

    MySQL的B+Tree中叶子节点的双向指针的作用:
    如果我们要查找>20的数据直接沿着指针指向,依次输出就好(不需要再根节点重新读取)。

    存储引擎

    同一个数据库下,不同的表可以使用不同的存储引擎。并不是同一个数据库下的存储引擎一定相同。

    MyISAM(非聚集)

    文件结构:
    .frm文件:存储表结构
    .MYI文件:存放索引字段——主键(primary key)
    .MYD文件:存放表中所有数据行

    select * from table where Col1 = 49

    当执行这条语句时(Col1作为主键索引)

    • 先从磁盘读取B+Tree,(.MYI文件)将节点49,50读入内存
    • 通过内存解析,得到data的值0x90
    • 再通过读取数据文件(.MYD文件),找到对应的地址(0x90)
    • 返回0x90地址所指向的数据行

    InnoDB(聚集)

    文件结构:

    • .frm文件:存储表结构
    • .idb文件:(.MYI+.MYD)索引+数据

    主键索引
    data数据存储索引所在行的所有字段。也就是叶子结点拥有这张表的所有数据。

    聚集(聚簇)索引: InnoDB的主键索引可以理解为一种聚集索引。其实就是我们的索引和表的数据放在一个文件中去存储。
    非聚集索引(稀疏索引): MyISAM中,他的索引文件和数据文件是分离的,这就是非聚集。
    总结: 聚集与否,取决于索引和数据是否在一个文件里。

    辅助索引
    辅助索引,要遍历两棵树。

    • 首先在辅助索引中遍历出Eric的主键值30
    • 接着在主键索引中遍历出主键为30的相对应的data的值


    关注问题:

    • 表数据文件本身就是按B+Tree组织的一个索引结构文件
    • 聚集索引-叶节点包含了完整的数据记录
    • 为什么InnoDB表必须有主键,并且推荐使用整型的自增主键?
      因为InnoDB底层的结构就是B+Tree,B+Tree必须要有主键形成索引生成相应的B+Tree。如果不人为建索引,那InnoDB会在后台生成RowID,将RowID作为索引。
      用整型是因为,在B+Tree的查找中,要对索引的大小进行比较,整型效率高,占存储少。
      自增:使用自增的话只要在最后(最右边叶子节点)加入元素或者新加节点就可以,如果不是自增的,那么新加入的元素可能要在中间插入,如果恰好要插入的节点用完了MySQL分配的16KB,那么就会导致索引重建(节点分裂+重新平衡),会浪费大量时间和资源。
    • 为什么非主键索引结构叶子节点存储的是主键值?(一致性和节省存储空间)

    一致性:如果对表进行修改,或者插入(统称为写数据的操作),那么主键索引维护(数据的插入,数据的修改,B+Tree的修改)好了之后,还要对辅助索引做相同的维护,这样就类似与多线程中,对一个共享变量的修改,所有引用共享变量的缓存区都要进行修改,之后才可以进行下一步的操作。这里也是一样,要等所以辅助索引的维护完成之后,操作才可以完全提交,否则就会出现数据不一致的问题。也类似于事务,必须所有操作完成,才可以正式提交,性能下降。而在辅助索引中,只存入主键,不存入其他数据,就可以很好的避免这一问题

    节省空间: 如果辅助索引中也是与主键索引一样的data值,那么每一个列都要把整张表的所有数据存一遍,这样会浪费大量的存储空间。

    联合索引底层数据结构

    所谓联合索引,就是把多个字段合并形成联合索引字段。
    如下图中的,1002为部门编号,staff为员工类别,1996-08-03是员工出生日期。
    将这一堆字段作为一个索引。
    这样做的好处是,不用一个一个字段去分别建立索引树,(比如有45个字段,是不是要建与主索引所对应的45个B+Tree呢?当然是不需要的,通过联合索引,我如果以每3个字段建立一个B+Tree,那么只要建立15个B+Tree,大大节约了存储空间,同时,由于是联合索引,如果同时查找员工部门编号,和员工类别,那么以下图的联合索引,只要遍历这一棵B+TRee就可以实现,而不需要遍历两个B+Tree,提高了执行效率)

    最左前缀法则

    如果是多列索引,就要遵守最左前缀法则。指的是查询从表的索引的最左前列开始并且不能跳过索引中的列。
    比如联合索引(No,type,birthday)
    那么如果查询想要正常使用索引,就必须:
    select * from table where No = 10001 AND type=‘Staff’
    如果出现:
    select * from table where No = 10001 AND birthda=‘1996-08-03’
    这种情况不满足最左前缀法则,不会使用联合索引查找,而是采取全部查找。
    注意:联合索引在使用的时候,是按照定义的顺序来执行的,如果缺少前列,或中间列,都无法实现,但是如果仅仅只是缺少后列(从第一个字段一直到中间某一字段连续存在,其后字段不存在),则联合索引仍然有效。

    内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: