|
% m5 ?4 z/ x, m6 @$ |. Q
什么是索引,为什么需要索引? What,Why,Where,How...记得在德国念书做第一个实验室
- G$ I& r: T- x; O 什么是索引,为什么需要索引?What,Why,Where,How...记得在德国念书做第一个实验室项目的时候,教授就给我们说:想认识任何一门技术,先从这四个词开始先来看这样一个例子,超市里面有成千上万种商品,要从里面找到黑人牙膏,如果从进门开始在货架上一个个去找,要什么时候才能找到... 。 ; C& K4 a" M* E) e; A3 {# n
于是超市老板设计了一套货品摆放的规则:一楼放生活用品类,二楼放食品类...一楼的货架又分洗漱用品类,洗涤用品类,五金类...洗漱用品类又按商品品牌进行归类...同一个品牌又按价格排序...以便快速找到客人想要的商品。
5 x- ?, B6 L; [2 D. H. i! t. u 与上述例子类比,我们数据库里存储了1000万条用户数据,要从中查询到name = “zengjing”的记录,如果一条一条去查,要花多长时间?我们先来看看此时数据库的行为,由于我们想要得到名字为zengjing的数据,在查询到第一个符合条件的行后,数据库并不会停止查询,因为可能还有其他符合条件的数据。
2 f2 p7 ^# [; g9 E/ P, o6 b 所以,必须一行行地查找直到最后一行,这就意味着数据库不得不检查上千万行数据才能找到所有姓名为zengjing的用户这就是所谓的全表扫描要花多长时间请大家自行脑洞...于是,此时需要索引对要查询的字段建立索引其实就是把该字段按照一定的方式排序。
7 ^- M7 c, D3 {& \& Z% k- G 注意!建立的索引只对该字段有用,如果查询的字段改变,那么这个索引也就无效了,比如上面数据库用户表是按照姓名的第一个字母排序的,那么你想要找手机号为18888888888的用户就不能用该索引了,还有就是如果索引太多也会降低查询的速度!
, A7 v; |3 S7 t5 k) H* t 探究数据结构欲了解索引的工作原理,必先了解其数据结构是什么? 如上述场景描述,显而易见,索引的目的就是加快查询速度什么样的数据结构适合做索引,换句话说,什么样的数据结构能加速查询?哈希,例如HashMap,查询/插入/修改/删除的平均时间复杂度都是O(1);。
: @1 d& `' C. z, O- C" Y 树,例如平衡二叉搜索树,查询/插入/修改/删除的平均时间复杂度都是O(lg(n));任何遇到二择其一的时候必然会去比较其优劣,或者,找到更适合的场景对数据结构有一定了解的童鞋都知道,在寻找值时哈希表效率极高,不管是读请求,还是写请求,哈希类型的索引,都要比树型的索引更快一些,那为什么,索引结构要设计成树型呢?。
j+ C, D2 e9 N( a7 @ (请驻足于此,思考10分钟...)我们来看看上面的场景中,SQL是这样的:select* from user where name = “zengjing”;此时确实是哈希索引更快,因为每次都只查询一条记录。
; ^0 V* B6 P" y; w+ a9 \ 哈希索引的工作方式是将列的值通过hash算法计算出hash值,将其作为索引的键值(key),和键值相对应实际的值(value)是指向该表中相应行的指针因为哈希表基本上可以看作是关联数组,一个典型的数据项就像“zengjing => 0x7a656e676a696e67″,而0x7a656e676a696e67是对内存中表中包含“zengjing”这一行的引用。
7 d2 m: K2 g6 Z$ }3 I8 S7 r: H, A0 y$ M 在哈希索引的中查询一个像“zengjing”这样的值,并得到对应行的在内存中的引用那么下面场景呢?group by, order by>, <哈希表是无序的数据结构,对于很多类型的查询语句哈希索引都无能为力。 ' {5 {1 W+ \( @
例如上面几个场景因为哈希表只适合查询键值对,也就是说查询相等的查询(例:WHERE name = “zengjing”)哈希表的键值映射也表明了其键的存储是无序的这就是为什么哈希索引通常不是数据库索引默认的数据结构,因为若将其作为索引的数据结构,并没有树那么灵活。 5 U6 M. j0 y4 j6 G1 k
二叉树 -> B Tree -> B + Tree都是树,WHY B + TREE?先看看这几棵树:1、二叉树二叉树,是最为大家所熟知的一种数据结构,这里就不多介绍了,它为什么不适合用作数据库索引?(请驻足于此,思考10分钟...) ' M1 B) t! R) Y
A. 当数据量大的时候,树的高度会比较高,数据量大的时候,查询会比较慢;B. 每个节点只存储一个记录,可能导致一次查询有很多次磁盘IO;2、B Tree 9 p- m t2 }' N6 ^
B Tree,由图中所示,其明显特点为:A. N叉搜索B. 叶子节点和非叶子节点都存储数据C. 中序遍历,可获得所有节点B Tree被作为实现索引的数据结构被创造出来,是因为它能够完美的利用“局部性原理”:
2 _1 `% l' B% N! U A. 内存读写块,磁盘读写慢,而且慢很多;B. 磁盘预读:磁盘读写并不是按需读取,而是按页预读,一次会读一页(4K)的数据,每次加载更多的数据,如果未来要读取的数据就在这一页中,可以避免未来的磁盘IO,提高效率; & C7 V5 c, V0 w8 T
C. 局部性原理:软件设计要尽量遵循“数据读取集中”与“使用到一个数据,大概率会使用其附近的数据”,这样磁盘预读能充分提高磁盘IO;B Tree为何适合做索引?A. 由于是m分叉的,高度能够大大降低; 9 B1 k5 m M x4 F7 i
B. 每个节点可以存储j个记录,如果将节点大小设置为页大小,例如4K,能够充分的利用预读的特性,极大减少磁盘IO;3、B + TreeB+ Tree,仍是N叉搜索树,在B Tree的基础上,做了一些改进:
9 ?' G8 \" ^/ _, C# G6 D4 s A. 非叶子节点不再存储数据,数据只存储在同一层的叶子节点上;B. 叶子之间,增加了链表,获取所有节点,不再需要中序遍历;这些改进让B+ Tree比B Tree有更优的特性:A. 范围查找,定位min与max之后,中间叶子节点,就是结果集,不用中序回溯;范围查询在SQL中用得很多,这是B+ Tree比BTree最大的优势。 b- G) ^+ F% |$ L, {
B. 叶子节点存储实际记录行,记录行相对比较紧密的存储,适合大数据量磁盘存储;非叶子节点存储记录的PK,用于查询加速,适合内存存储;C. 非叶子节点,不存储实际记录,而只存储记录的KEY的话,那么在相同内存的情况下,B+ Tree能够存储更多索引; : v3 u, c# o }: h" B$ Q. C* g: B
最后,量化说下,为什么N叉的B+ Tree比二叉搜索树的高度有很大很大降低?大概计算一下:A. 局部性原理,将一个节点的大小设为一页,一页4K,假设一个KEY有8字节,一个节点可以存储500个KEY,即j=500 $ g2 d& K+ C, U: a/ x
B. N叉树,大概n/2<= j <=n,即可以差不多是1000叉树C. 那么:一层树:1个节点,1*500个KEY,大小4K二层树:1000个节点,1000*500=50W个KEY,大小1000*4K=4M
$ k+ J# A* ^8 J% f6 K" P( s9 d 三层树:1000*1000个节点,1000*1000*500=5亿个KEY,大小1000*1000*4K=4G可以看到,存储大量的数据(5亿),并不需要太高树的深度(高度3),索引也不是太占内存(4G)。
. L6 [) y1 I6 R0 K9 U; f 总结数据库的索引最常用B+树:A. 很适合磁盘存储,能够充分利用局部性原理,磁盘预读;B. 很低的树高度,能够存储大量数据;C. 索引本身占用的内存很小;D. 能够很好的支持单点查询,范围查询,有序性查询。
/ R# Z) ^9 ~3 A* b. Q1 h8 z- W. P# `; _" V4 w
' X5 c t" R y" H
8 e9 q6 g a1 ^$ l) B; n. w/ w t. ?
: w5 Y3 \, M. Y, x6 w, [7 \- T |