数据库中的索引

目录

一、基本内容

1.索引本质

2.索引结构

哈希表

B树(平衡多路查找树)

B+树

3.索引存储量

磁盘IO

局部性原理与磁盘预读

访问固态硬盘(SSD)的原理

4.MySQL中的索引

MYISAM中的索引实现

InnoDB中索引实现

疑惑:联合索引的结构是怎样的

数据库索引在什么情况下失效

索引的优化

sql语句的优化

二、参考文章


一、基本内容

1.索引本质

一种帮助数据库高效获取数据的数据结构(即优化查询的数据结构,用来快速查找具有特定列值的行数据),存储在磁盘中

适合做索引的数据结构(有序的)

2.索引结构

哈希表

哈希表(Hash table,也叫散列表), 是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。

哈希表hash table(key,value) 的做法其实很简单,就是把Key通过一个固定的算法函数既所谓的哈希函数转换成一个整型数字,然后就将该数字对数组长度进行取余,取余结果就当作数组的下标,将value存储在以该数字为下标的数组空间里。

而当使用哈希表进行查询的时候,就是再次使用哈希函数将key转换为对应的数组下标,并定位到该空间获取value,如此一来,就可以充分利用到数组的定位性能进行数据定位。

哈希表最大的优点,就是把数据的存储和查找消耗的时间大大降低,几乎可以看成是常数时间;而代价仅仅是消耗比较多的内存。然而在当前可利用内存越来越多的情况下,用空间换时间的做法是值得的。另外,编码比较容易也是它的特点之一。 哈希表又叫做散列表,分为“开散列” 和“闭散列”。

冲突处理使用拉链法(一个数组,每个元素后是一个链表)

MySQL使用B树而非哈希索引的原因:和B树索引相比,hash索引在使用上有很大的局限性:

1)无序性,导致无法范围查找和索引排序。 
>、<、between等范围查询无法使用索引。
2)对完整的key计算hash,所以不支持部分匹配。 
比如对多个列创建hash索引,查找时条件必须这些列精确匹配,才能使用到hash索引。都精确匹配了,更别谈什么索引覆盖。 
再比如,使用like 'hash%'进行前缀匹配,也无法使用hash索引。
3)由于hash索引实际只保存了数据对应的行指针,所以不能避免读取数据行(说白了还是没有索引覆盖的功能)。
4)当产生hash碰撞的时候,数据库要遍历拉链中所有的行指针,逐个取出数据行进行比较,数据量越大,冲突越多,查找代价越高。
由于hash索引的上述缺点,所以实际使用hash索引的情况很少,MySQL除了Memory存储引擎和NDB分布式存储引擎,其他大部分存储引擎默认使用B树索引。

 

B树(平衡多路查找树)

不理解平衡二叉树的可以先从参考文章看下什么是平衡二叉树

B树类似普通的平衡二叉树,不同的一点是B树允许每个节点有更多的子节点(看图就行,描述啥的可以不看)

B-Tree结构的数据可以让系统高效的找到数据所在的磁盘块。为了描述B-Tree,首先定义一条记录为一个二元组[key, data] ,key为记录的键值,对应表中的主键值,data为一行记录中除主键外的数据。对于不同的记录,key值互不相同。

一棵m阶的B-Tree有如下特性: 
1. 每个节点最多有m个孩子。 
2. 除了根节点和叶子节点外,其它每个节点至少有Ceil(m/2)个孩子。 
3. 若根节点不是叶子节点,则至少有2个孩子 
4. 所有叶子节点都在同一层,且不包含其它关键字信息 
5. 每个非终端节点包含n个关键字信息(P0,P1,…Pn, k1,…kn) 
6. 关键字的个数n满足:ceil(m/2)-1 <= n <= m-1 
7. ki(i=1,…n)为关键字,且关键字升序排序。 
8. Pi(i=1,…n)为指向子树根节点的指针。P(i-1)指向的子树的所有节点关键字均小于ki,但都大于k(i-1)

优点:

1.一个节点有多个元素,并且是非递减排列的

2.所有叶节点具有相同的深度,等于树高h

缺点:

1.一个节点存放多少元素合适

2.范围查找时需要回溯节点(就是能回到上个节点)

B+树

与B树不同点:

1.叶子节点冗余了所有的非叶子节点

2.每个叶子节点增加一个指向相邻叶子节点的指针

优点:叶子节点有指向相邻节点的指针,提高了范围查询的效率

缺点:一个节点中存放多少元素合适

另外B+树相比AVL树层数低,降低了磁盘IO的操作次数

 

3.索引存储量

存放多少元素合适,从数据读取讲起:

1.B树或B+树中的节点为一页或页的倍数大小比较合适

2.一个好的索引结构,在使用它的过程中要尽量少的进行磁盘IO操作

磁盘IO

索引一般以文件形式存储在磁盘上,索引检索需要磁盘I/O操作。与主存不同,磁盘I/O存在机械运动耗费,因此磁盘I/O的时间消耗是巨大的。(这一段要知道磁盘存储刚好是操作系统页的倍数这一点)

一个磁盘由大小相同且同轴的圆形盘片组成,磁盘可以转动(各个磁盘必须同步转动)。在磁盘的一侧有磁头支架,磁头支架固定了一组磁头,每个磁头负责 存取一个磁盘的内容。磁头不能转动,但是可以沿磁盘半径方向运动(实际是斜切向运动),每个磁头同一时刻也必须是同轴的,即从正上方向下看,所有磁头任何 时候都是重叠的(不过目前已经有多磁头独立技术,可不受此限制)。

盘片被划分成一系列同心环,圆心是盘片中心,每个同心环叫做一个磁道,所有半径相同的磁道组成一个柱面。磁道被沿半径线划分成一个个小的段,每个段叫做一个扇区,每个扇区是磁盘的最小存储单元。为了简单起见,我们下面假设磁盘只有一个盘片和一个磁头。

当需要从磁盘读取数据时,系统会将数据逻辑地址传给磁盘,磁盘的控制电路按照寻址逻辑将逻辑地址翻译成物理地址,即确定要读的数据在哪个磁道,哪个 扇区。为了读取这个扇区的数据,需要将磁头放到这个扇区上方,为了实现这一点,磁头需要移动对准相应磁道,这个过程叫做寻道,所耗费时间叫做寻道时间,然 后磁盘旋转将目标扇区旋转到磁头下,这个过程耗费的时间叫做旋转时间。

局部性原理与磁盘预读

由于存储介质的特性,磁盘本身存取就比主存慢很多,再加上机械运动耗费,磁盘的存取速度往往是主存的几百分分之一,因此为了提高效率,要尽量减少磁 盘I/O。为了达到这个目的,磁盘往往不是严格按需读取,而是每次都会预读,即使只需要一个字节,磁盘也会从这个位置开始,顺序向后读取一定长度的数据放 入内存。这样做的理论依据是计算机科学中著名的局部性原理:

当一个数据被用到时,其附近的数据也通常会马上被使用。

程序运行期间所需要的数据通常比较集中。

由于磁盘顺序读取的效率很高(不需要寻道时间,只需很少的旋转时间),因此对于具有局部性的程序来说,预读可以提高I/O效率。

预读的长度一般为页(page)的整倍数。页是计算机管理存储器的逻辑块,硬件及操作系统往往将主存和磁盘存储区分割为连续的大小相等的块,每个存 储块称为一页(在许多操作系统中,页得大小通常为4k),主存和磁盘以页为单位交换数据。当程序要读取的数据不在主存中时,会触发一个缺页异常,此时系统 会向磁盘发出读盘信号,磁盘会找到数据的起始位置并向后连续读取一页或几页载入内存中,然后异常返回,程序继续运行

访问固态硬盘(SSD)的原理

1。Host是通过LBA(Logical BlockAddress,逻辑地址块)访问SSD的,Host访问SSD的基本单元叫用户页(一般操作系统的页大小为4K,Host Page) 
2.SSD内部,SSD主控与Flash之间是Flash Page为基本单元访问Flash的,我们称Flash Page为物理页(Physical Page) 
3.Host每写入一个Host Page, SSD主控会找一个Physical Page把Host数据写入,SSD内部同时记录了这样一条映射(Map)。有了这样一个映射关系后,下次Host需要读某个Host Page 时,SSD就知道从Flash的哪个位置把数据读取上来。 SSD内部维护了一张映射表(Map Table),Host每写入一个Host Page,就会产生一个新的映射关系,这个映射关系会加入(第一次写)或者更改(覆盖写)Map Table;当读取某个Host Page时, SSD首先查找Map Table中该Host Page对应的Physical Page,然后再访问Flash读取相应的Host数据。

4.大部分映射是存储在FLASH里面,还有一部分存储在片上RAM上。当Host需要读取一笔数据时,对有板载DRAM的SSD来说,只要查找DRAM当中的映射表,获取到物理地址后访问Flash从而得到Host数据.这期间只需要访问一次FlashH;而对Sandforce的SSD来说,它首先看看该Host Page对应的映射关系是否在RAM内,如果在,那好办,直接根据映射关系读取FLASH;如果该映射关系不在RAM内,那么它首先需要把映射关系从FLASH里面读取出来,然后再根据这个映射关系读取Host数据,这就意味着相比有DRAM的SSD,它需要读取两次FLASH才能把HOST数据读取出来,底层有效带宽减半。

4.MySQL中的索引

MYISAM中的索引实现

MYISAM中叶节点的数据区域存储的是数据记录的地址

主键索引和辅助索引(其他索引)存的方法一样,都要进行两次磁盘IO(一次取索引,一次取数据)

InnoDB中索引实现

在InnoDB中,表数据文件本身就是按B+Tree组织的一个索引结构,这棵树的叶节点data域保存了完整的数据记录。

InnoDB主键索引也负责存储数据,在用辅助索引查询数据时会先将其保存的主键索引找出,再去主键索引中找出数据

若是用InnoDB建表时不使用主键的话,InnoDB会去寻找一个唯一索引当中主键索引,若是也没有唯一索引则会建一个隐含的主键索引

 

 

构建索引注意索引的--命中率:索引值去重后的数量/所有的记录数量(如:create index id_name on table (id,name(4)),name字段选取4或者其他数字的大小影响命中率)

使用索引牢记:索引的底层是B+树,索引的查询遵循树的查找匹配,最左前缀原则(索引由个字段拼凑,会按顺序从左到右进行字段值匹配,选取不同的树节点)

MySQL会对查找语句中字段的顺序优化,找到有索引的(如,上面的id_name索引,在where语句使用:where name='xxx' and id=123,MySQL是会优化成:where id=123 and name='xxx',从而利用id_name索引提高查询效率)

 

疑惑:联合索引的结构是怎样的

比方说联合索引 (col1, col2,col3),我知道在逻辑上是先按照col1进行排序再按照col2进行排序最后再按照col3进行排序。因此如果是select * from table where col1 = 1 and col3 = 3的话,只有col1的索引部分能生效。但是其物理结构上这个联合索引是怎样存在的,我想不懂。

解答:联合索引的结构[8]

上网查阅了许多资料,总算有点眉目了。

假设这是一个多列索引(col1, col2,col3),对于叶子节点,是这样的:

PS:该图改自《MySQL索引背后的数据结构及算法原理》一文的配图。

也就是说,联合索引(col1, col2,col3)也是一棵B+Tree,其非叶子节点存储的是第一个关键字的索引,而叶节点存储的则是三个关键字col1、col2、col3三个关键字的数据,且按照col1、col2、col3的顺序进行排序。

配图可能不太让人满意,因为col1都是不同的,也就是说在col1就已经能确定结果了。自己又画了一个图(有点丑),col1表示的是年龄,col2表示的是姓氏,col3表示的是名字。如下图:

\

PS:对应地址指的是数据记录的地址。

如图,联合索引(年龄, 姓氏,名字),叶节点上data域存储的是三个关键字的数据。且是按照年龄、姓氏、名字的顺序排列的。

因此,如果执行的是:
select * from STUDENT where 姓氏='李' and 名字='安';
或者
select * from STUDENT where 名字='安';
那么当执行查询的时候,是无法使用这个联合索引的。因为联合索引中是先根据年龄进行排序的。如果年龄没有先确定,直接对姓氏和名字进行查询的话,就相当于乱序查询一样,因此索引无法生效。因此查询是全表查询。

如果执行的是:
select * from STUDENT where 年龄=1 and 姓氏='李';
那么当执行查询的时候,索引是能生效的,从图中很直观的看出,age=1的是第一个叶子节点的前6条记录,在age=1的前提下,姓氏=’李’的是前3条。因此最终查询出来的是这三条,从而能获取到对应记录的地址。
如果执行的是:
select * from STUDENT where 年龄=1 and 姓氏='黄' and 名字='安';
那么索引也是生效的。

而如果执行的是:
select * from STUDENT where 年龄=1 and 名字='安';
那么,索引年龄部分能生效,名字部分不能生效。也就是说索引部分生效。

因此我对联合索引结构的理解就是B+Tree是按照第一个关键字进行索引,然后在叶子节点上按照第一个关键字、第二个关键字、第三个关键字…进行排序。

 

数据库索引在什么情况下失效

(1)条件中用or(这就是为什么少用or的原因)

(2)对于多列(复合、联合)索引,不是使用的第一部分,则不会使用索引。(最左匹配原则或者叫做最左前缀原则)

比如:Index_SoftWareDetail索引包含(a,b,c) 三列,但是查询条件里面,没有a,b 列,只有c 列,那么 Index_SoftWareDetail索引也不起作用。

例如:bc   c   acb bac 都是不行的

(3)like的模糊查询以%开头,索引失效

(4)如果列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不会使用索引

(5)如果MySQL预计使用全表扫描要比使用索引快,则不使用索引

(6)判断索引列是否不等于某个值时。‘!=’操作符。比如:select * from SoftWareDetailInfo where SoftUseLine != 0

(7)对索引列进行运算。这里运算包括+-*/等运算。也包括使用函数。

比如:

select * from SoftWareDetailInfo where SoftUseLine +0= 0

此时索引不起作用。

select * from SoftWareDetailInfo where count(SoftUseLine) = 0

此时索引也不起作用。

也就是说如果不是直接判断索引字段列,而是判断运算或其它函数处理后的索引列索引均不起作用。

(8)索引字段进行判空查询时。也就是对索引字段判断是否为NULL时。语句为is null 或is not null。

  比如:select * from SoftWareDetailInfo where CreateTime is null 此时就不检索time字段上的索引表了。也就是索引在这条语句执行时失效了。

  接着再执行

select * from SoftWareDetailInfo where CreateTime = '2015-04-11 00:00:00' 此时就会检索索引表了。索引又起作用了。

(9)范围列可以用到索引(联合索引必须是最左前缀),但是范围列后面的列无法用到索引

索引的优化

①尽量不要使用左模糊和全模糊,如果需要可以使用搜索引擎来解决

②union,in和or都可以命中索引,建议使用in

③负向条件查询不能使用索引,可以优化为in查询

负向条件查询有:!=  <>  not in  not like等等

例如:select * from user where status!=1 and status!=2

优化为:select * from user where status in (0,3,4);

④合理使用联合索引的最左前缀原则

如果在(a,b,c)三个字段上建立联合索引,那么它能够加快 a | (a,b) | (a,b,c) 三组查询速度。

比如说把(username,password)建立了联合索引,因为业务上几乎没有password的单条件查询,而有很多username的单条件查询需求,所以应该建立(username,password)的联合索引,而不要建立(password,username)的联合索引

注意:(1)建立联合索引的时候,要把查询频率较高的列放在最左边

(2)如果建立了(a,b)索引,就不必再独立建立a索引。同理如果建立了(a,b,c)联合索引,就不必再独立建立a,(a,b)索引

(3)存在非等号和等号混合判断条件时,在建索引时,请把等号条件的列前置。如     where a>? and b=?,那么即使 a 的区分度更高,也必须把 b 放在索引的最前列。

(4)最左前缀原则,并不是要求where后的顺序和联合索引的一致。下面的 SQL 语句也可以命中 (login_name, passwd) 这个联合索引。

selectuid, login_time from user where passwd=? andlogin_name=?
但还是建议 where 后的顺序和联合索引一致,养成好习惯。

⑤把计算放到业务层而不是数据库层。(因为对索引列进行运算,不能命中索引)

⑥表数据比较少、更新十分频繁、数据区分度不高的字段上不宜建立索引。

一般区分度在80%以上的时候就可以建立索引,区分度可以使用 count(distinct(列名))/count(*) 来计算。

⑦强制类型转换会全表扫描

例如:如果phone字段是varchar类型,则下面的sql不能命中索引

select * from user where phone = 18838003017

可以优化为:select * from user where phone = ‘18838003017’

⑧利用覆盖索引进行查询操作,避免回表

select uid,login_time from user where username=? and password=?

如果建立了(username,password,login_time)的联合索引,由于login_time已经建立在索引中了,被查询的username和password就不用去row上获取数据了,从而加速查询

⑨在order by和group by中要注意索引的有序性

如果order by是组合索引的一部分,应该将该字段放在组合索引的最后

例如:where a=? and b=? order by c ->可以建立联合索引(a,b,c)

如果索引中有范围查找,则索引的有序性无法利用

例如:where a>10 order by b ->索引(a,b)无法排序

⑩建立索引的列,不许为null

单列索引不存 null 值,复合索引不存全为 null 的值,如果列允许为 null,可能会得到“不符合预期”的结果集,所以,请使用 not null 约束以及默认值。

sql语句的优化

①能用到索引尽量用到索引.对索引的优化实际上就是sql语句的调优

②任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段。

③尽量使用where,而不要使用having

④尽量使用多表查询,不要使用子查询

⑤where后的and.or左右执行顺序是从右至左

运算符为and时--尽量把为假的放在右边

运算符为or时--尽量把为真的放在右边

 

 

二、参考文章

[1] 索引的本质:

https://www.cnblogs.com/luoluo01/archive/2012/10/26/2741344.html

[2] 哈希表(Hash Table)原理及其实现:

https://blog.csdn.net/c602273091/article/details/54798805/

[3] 二叉树、平衡二叉树、b-tree、b+tree详解:

https://blog.csdn.net/ifollowrivers/article/details/73614549

[4] 存储笔记--ssd基本原理篇

https://blog.csdn.net/u010709783/article/details/79078853

[5] 数据库索引:

https://blog.csdn.net/qq_36071795/article/details/83956068

[6] 数据库索引全解(What is Database Index):

https://blog.csdn.net/qq_32690999/article/details/78021040

[7] 视频:MySQL索引底层实现

https://www.bilibili.com/video/av37078547

[8] 联合索引在B+树上的结构介绍

https://blog.csdn.net/zgjdzwhy/article/details/84062105

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值