• 索引原理(平衡树数据结构)


    索引索引索引

    本质:通过不断地缩小想要获取数据的范围来筛选出最终想要的结果,同时把随机的事件变成顺序的事件,也就是说,有了这种索引机制,我们可以总是用同一种查找方式来锁定数据

    想要了解索引的原理就必须了解一种数据结构——平衡树(b tree或b+ tree),也有写索引是用哈希桶作为其数据结构,但是主流的RDBMS都是把平衡树作为默认的索引存储结构

    这里的平衡树,类似我们熟悉的二叉树,但不等于二叉树,存储结构也是有根节点,叶结点,真正的数据只存在叶子结点上,其他非叶子结点只存储指引搜索的方向。事实上,创建表时如果未指定主键,则数据无序地放置在磁盘存储器上,一列一列很整齐地存放着,但是没有规律,如果指定了主键,数据存储结构立刻变为树状结构,也就是所说的平衡树结构,同时生成了一个聚集索引,这就是为什么一个表只能有一个主键, 一个表只能有一个聚集索引,因为主键的作用就是把表的数据存储格式转换成索引(平衡树)的格式放置。

    例如这条SQL语句:

    select * from kxf where id=20;

    首先根据索引定位到20这个值所在的叶结点,然后再通过叶结点取到id等于20的数据行。 这里不讲解平衡树的运行细节, 但是树有多少层, 从根节点至叶节点就只需要经过多少次查找就能得到结果,这里树有三层,也就是说只需要三次IO,如下图

    假如一张表有一亿条数据,需要查找其中某一条,按照常规逻辑,一条一条的去匹配的话,最坏的情况下需要匹配一亿次才能得到结果,时间复杂度就是O(n),这显然无法接受,而且这一亿条数据显然不能一次性读入内存供程序使用,因此,这一亿次匹配在不仅缓存优化的情况下就是一亿次IO开销,以现在磁盘的IO能力和CPU运算能力,有可能需要几个月才能得出结果。如果把这张表转换成平衡树结构,假设这棵树有10层,那就只需要10次IO开销就能查找到数据,速度以指数级别提升。

    所以假设当前数据表的数据为N,每个磁盘块的数据项的数量是m,则有h=㏒(m+1)N,当数据量N一定的情况下,m越大,h越小;而m = 磁盘块的大小 / 数据项的大小,磁盘块的大小也就是一个数据页的大小,是固定的,如果数据项占的空间越小,数据项的数量越多,树的高度越低。这就是为什么每个数据项,即索引字段要尽量的小,比如int占4字节,要比bigint8字节少一半。这也是为什么b+树要求把真实的数据放到叶子节点而不是内层节点,一旦放到内层节点,磁盘块的数据项会大幅度下降,导致树增高。当数据项等于1时将会退化成线性表。

    讲完聚集索引,接下来说一下非聚集索引,也就是我们平时经常用到的常规索引

    非聚集索引和聚集索引一样, 同样是采用平衡树作为索引的数据结构。索引树结构中各节点的值来自于表中的索引字段, 假如给user表的name字段加上索引 , 那么索引就是由name字段中的值构成,在数据改变时, DBMS需要一直维护索引结构的正确性。如果给表中多个字段加上索引 , 那么就会出现多个独立的索引结构,每个索引(非聚集索引)互相之间不存在关联。 

    非聚集索引和聚集索引的区别在于,通过聚集索引可以直接查找到所需要的数据,而通过非聚集索引可以查找到记录对应的主键,然后再通过聚集索引查找到所需要的记录,查找步骤如下图:

    无论采用哪种方式查询表,最终都会通过聚集索引来定位数据,聚集索引(主键)是通往真实数据所在的唯一路径。

    然而, 有一种例外可以不使用聚集索引就能查询出所需要的数据, 这种非主流的方法 称之为覆盖索引查询, 也就是平时所说的复合索引或者多字段索引查询。 文章上面的内容已经指出, 当为字段建立索引以后, 字段中的内容会被同步到索引之中, 如果为一个索引指定两个字段, 那么这个两个字段的内容都会被同步至索引之中。

    先看下面这个SQL语句

    //建立索引

    create index index_birthday on user_info(birthday);

    //查询生日在1991年11月1日出生用户的用户名

    select user_name from user_info where birthday = '1991-11-1'

    这句SQL语句的执行过程如下

    首先,通过非聚集索引index_birthday查找birthday等于1991-11-1的所有记录的主键ID值

    然后,通过得到的主键ID值执行聚集索引查找,找到主键ID值对就的真实数据(数据行)存储的位置

    最后, 从得到的真实数据中取得user_name字段的值返回, 也就是取得最终的结果

    我们把birthday字段上的索引改成双字段的覆盖索引

    create index index_birthday_and_user_name on user_info(birthday, user_name);

    这句SQL语句的执行过程就会变为

    通过非聚集索引index_birthday_and_user_name查找birthday等于1991-11-1的叶节点的内容,然而, 叶节点中除了有user_name表主键ID的值以外, user_name字段的值也在里面, 因此不需要通过主键ID值的查找数据行的真实所在, 直接取得叶节点中user_name的值返回即可。 通过这种覆盖索引直接查找的方式, 可以省略不使用覆盖索引查找的后面两个步骤, 大大的提高了查询性能。

    下面的表总结了何时使用聚集索引或非聚集索引:

    动作描述

    使用聚集索引

    使用非聚集索引

    列经常被分组排序

    返回某范围内的数据

    不应

    一个或极少不同值

    不应

    不应

    小数目的不同值

    不应

    大数目的不同值

    不应

    频繁更新的列

    不应

    外键列

    主键列

    频繁修改索引列

    不应

    参考博主:https://www.cnblogs.com/aspwebchh/p/6652855.html

  • 相关阅读:
    c++中的const关键字
    用类模板实现容器存储自定义数据类型(类似于STL里面的vector)
    用类模板实现容器存储普通数据类型(类似于STL里面的vector)
    pgmpy包的安装,以及conda的安装
    【SQL语言】SQL语言基础02
    【win7系统】win7系统常遇到的一些问题
    【博客收集】一些关于个人博客、程序员书单、程序资源、学习方法的博文(持续更新中~)!
    【分享】一些好的网站与技术博客
    【ORACLE】Oracle 忘记用户名和密码的和用户被锁定的解决办法
    【ORACLE】SQL Developer 与 PL/SQL Developer与SQL PLUS的使用问题
  • 原文地址:https://www.cnblogs.com/heibaijianpan/p/10706288.html
Copyright © 2020-2023  润新知