索引索引索引
本质:通过不断地缩小想要获取数据的范围来筛选出最终想要的结果,同时把随机的事件变成顺序的事件,也就是说,有了这种索引机制,我们可以总是用同一种查找方式来锁定数据
想要了解索引的原理就必须了解一种数据结构——平衡树(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