带你整理面试过程中关于数据库的索引的相关知识点

Posted 南淮北安

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了带你整理面试过程中关于数据库的索引的相关知识点相关的知识,希望对你有一定的参考价值。

文章目录

一、数据库索引

数据库索引是一种数据结构。

通过增加额外的写操作和存储空间来维护数据库索引,可以提高从数据库中读取数据的速度。

通过索引,不需要搜索数据库的每一条记录,就可以快速地定位到特定的数据。

索引可以建在在表中某一个字段或多个字段之上。总而言之:数据库索引是一种数据结构

1. 创建普通索引

//如果是CHAR,VARCHAR类型,length可以小于字段实际长度;如果是BLOB和TEXT类型,必须指定 length,下同。
CREATE INDEX indexName ON mytable(username(length)); 
//修改表结构
ALTER mytable ADD INDEX [indexName] ON (username(length)) 
//创建表的时候直接指定
CREATE TABLE mytable(  
	ID INT NOT NULL,   
	username VARCHAR(16) NOT NULL,  
	INDEX [indexName] (username(length))  
); 
//删除索引
DROP INDEX [indexName] ON mytable; 

2. 创建唯一索引

它与前面的普通索引类似,不同的就是:索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一。它有以下几种创建方式:

//创建索引
CREATE UNIQUE INDEX indexName ON mytable(username(length)) 
//修改表结构
ALTER mytable ADD UNIQUE [indexName] ON (username(length)) 
//创建表的时候直接指定
CREATE TABLE mytable(  
	ID INT NOT NULL,   
	username VARCHAR(16) NOT NULL,  
	UNIQUE [indexName] (username(length))  
);  

3. 主键索引

它是一种特殊的唯一索引,不允许有空值。一般是在建表的时候同时创建主键索引:

CREATE TABLE mytable(  
	ID INT NOT NULL,   
	username VARCHAR(16) NOT NULL,  
	PRIMARY KEY(ID)  
);  

一个表只能有一个主键

4. 建立索引的时机

到这里我们已经学会了建立索引,那么我们需要在什么情况下建立索引呢?一般来说,在WHERE和JOIN中出现的列需要建立索引,但也不完全如此,因为mysql只对<,<=,=,>,>=,BETWEEN,IN,以及某些时候的LIKE才会使用索引。例如:

SELECT t.Name FROM mytable t 
LEFT JOIN mytable m ON t.Name=m.username WHERE m.age=20 AND m.city='郑州' 

此时就需要对city和age建立索引,由于mytable表的userame也出现在了JOIN子句中,也有对它建立索引的必要。

刚才提到只有某些时候的LIKE才需建立索引。因为在以通配符%_开头作查询时,MySQL不会使用索引。例如下句会使用索引:

SELECT * FROM mytable WHERE username like'admin%' 

而下句就不会使用:

SELECT * FROM mytable WHEREt Name like'%admin' 

因此,在使用LIKE时应注意以上的区别。

5. 索引的 不足之处

(1)虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件。
(2)建立索引会占用磁盘空间的索引文件。一般情况这个问题不太严重,但如果你在一个大表上创建了多种组合索引,索引文件的会膨胀很快。

二、数据库索引的作用

(1)用于支持快速地查找到数据

若没有索引,通常需要遍历所有记录才能找到相应地数据(O(N));而通过索引,一般只需要O(log(N))次就可以定位到数据,提高了查找效率

(2)管理数据库约束

索引通常还会被用于管理数据库约束,例如UNIQUE, EXCLUSION, PRIMARY KEY 和 FOREIGN KEY。当一个索引被定义为UNIQUE时,数据库同时创建一个隐式的约束。

三、索引的原理

首先需要理解 B+ Tree:一篇文章带你完整复习二叉树、二叉搜索树、平衡二叉树、B树和B+树

我们平时建表的时候都会为表加上主键, 在某些关系数据库中, 如果建表时不指定主键,数据库会拒绝建表的语句执行。

事实上, 一个加了主键的表,并不能被称之为表。一个没加主键的表,它的数据无序的放置在磁盘存储器上,一行一行的排列的很整齐, 跟我认知中的表很接近。

如果给表上了主键,那么表在磁盘上的存储结构就由整齐排列的结构转变成了树状结构,也就是上面说的平衡树结构,换句话说,就是整个表就变成了一个索引。

没错, 再说一遍, 整个表变成了一个索引,也就是所谓的聚集索引。 这就是为什么一个表只能有一个主键, 一个表只能有一个聚集索引,因为主键的作用就是把表的数据格式转换成索引(平衡树)的格式放置


其中树的所有结点(底部除外)的数据都是由主键字段中的数据构成,也就是通常我们指定主键的id字段。最下面部分是真正表中的数据。 假如我们执行一个SQL语句:

select * from table where id = 1256;

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


假如一张表有一亿条数据 ,需要查找其中某一条数据,按照常规逻辑, 一条一条的去匹配的话, 最坏的情况下需要匹配一亿次才能得到结果,用大O标记法就是O(n)最坏时间复杂度,这是无法接受的,而且这一亿条数据显然不能一次性读入内存供程序使用, 因此, 这一亿次匹配在不经缓存优化的情况下就是一亿次IO开销,以现在磁盘的IO能力和CPU的运算能力, 有可能需要几个月才能得出结果 。

如果把这张表转换成平衡树结构(一棵非常茂盛和节点非常多的树),假设这棵树有10层,那么只需要10次IO开销就能查找到所需要的数据, 速度以指数级别提升,用大O标记法就是O(log n),n是记录总树,底数是树的分叉数,结果就是树的层次数。换言之,查找次数是以树的分叉数为底,记录总数的对数,用公式来表示就是


用程序来表示就是Math.Log(100000000,10),100000000是记录数,10是树的分叉数(真实环境下分叉数远不止10), 结果就是查找次数,这里的结果从亿降到了个位数。因此,利用索引会使数据库查询有惊人的性能提升。

然而, 事物都是有两面的, 索引能让数据库查询数据的速度上升, 而使写入数据的速度下降,原因很简单的, 因为平衡树这个结构必须一直维持在一个正确的状态, 增删改数据都会改变平衡树各节点中的索引数据内容,破坏树结构, 因此,在每次数据改变时, DBMS必须去重新梳理树(索引)的结构以确保它的正确,这会带来不小的性能开销,也就是为什么索引会给查询以外的操作带来副作用的原因。

三、聚集索引/聚簇索引

1. 聚集索引/聚簇索引

聚集索引是指数据库表行中数据的物理顺序与键值的逻辑(索引)顺序相同

一个表只能有一个聚集索引,因为一个表的物理顺序只有一种情况,所以,对应的聚集索引只能有一个。

如果某索引不是聚集索引,则表中的行物理顺序与索引顺序不匹配,与非聚集索引相比,聚集索引有着更快的检索速度。如下图,叶节点中直接包含了具体数据。

2. 非聚集索引/非聚簇索引

与聚集索引不同,非聚集索引的逻辑顺序与磁盘上行的物理存储顺序不同。磁盘上的数据可以随意分布,而通过非聚集索引,可以在逻辑上为数据排序。

如下图,叶节点没有包含具体的数据,而是包含了一个指向具体数据的指针

当索引通过二叉树的形式进行描述时,我们可以这样区分聚集与非聚集索引的区别:聚集索引的叶节点就是最终的数据节点,而非聚集索引的叶节仍然是索引节点,但它有一个指向最终数据的指针

四、MyISAM索引实现

MyISAM引擎使用B+Tree作为索引实现,并且所有的索引都是非聚集索引。
下图是主键索引:


若在Col2上建立辅助索引,其依然是一个非聚集索引,与主键索引类似:


MyISAM引擎中,使用索引查找数据时,先通过索引获取到数据的物理地址,然后通过物理地址读取数据

五、InnoDB 索引实现

InnoDB引擎同样使用B+Tree作为索引实现,但与MyISAM不同,在InnoDB引擎中,主键索引是聚集索引,而辅助索引则是非聚集索引。下图是主键索引:

若在Col2上建立辅助索引,则是一个非聚集索引,叶节点的值为数据的主键:

在InnoDB中,通过主键索引,可以直接获取到具体的数据;而通过辅助索引,在叶节点获取到的是数据的主键,然后再通过主键索引最终获取到数据

六、联合索引/多列索引

在上面的介绍中,我们主要是针对一个字段建立索引,而实际上,可以建立一个基于多个字段的索引。假设某张表中有a,b,c,d四个字段。现在在a,b,c上建立索引(a,b,c)(注意: a,b,c顺序不同建立的是不同的索引)。则索引首先会按a字段排序;在a字段相同的情况下按照b字段排序;在a,b字段相同的情况下按照c字段排序,以此类推。。。

CREATE TABLE mytable(  
	ID INT NOT NULL,   
	username VARCHAR(16) NOT NULL,  
	city VARCHAR(50) NOT NULL,  
	age INT NOT NULL 
);  

为了进一步榨取MySQL的效率,就要考虑建立组合索引。就是将 name, city, age 建到一个索引里:

ALTER TABLE mytable ADD INDEX name_city_age (name(10),city,age); 

建表时,usernname长度为 16,这里用 10。这是因为一般情况下名字的长度不会超过10,这样会加速索引查询速度,还会减少索引文件的大小,提高INSERT的更新速度。

七、最左前缀匹配原则

当建立联合索引时,该索引的所有最左前缀匹配可以用于优化查找。

以上面建立的(a,b,c)索引为例,其所有最左前缀匹配为(a),(a,b),(a,b,c)。即涉及到(a),(a,b),(a,b,c)的查找都可以利用索引(a,b,c),但涉及(a,c)的查找无法利用索引(a,b,c),因为(a,c)不满足最左前缀匹配原则。

如果分别在 usernname,city,age上建立单列索引,让该表有3个单列索引,查询时和上述的组合索引效率也会大不一样,远远低于我们的组合索引。虽然此时有了三个索引,但MySQL只能用到其中的那个它认为似乎是最有效率的单列索引。

建立这样的组合索引,其实是相当于分别建立了下面三组组合索引:

usernname,city,age  
usernname,city  
usernname  

为什么没有 city,age这样的组合索引呢?这是因为MySQL组合索引“最左前缀”的结果。

原理:
最左匹配原则都是针对联合索引来说的,所以我们有必要了解一下联合索引的原理。了解了联合索引,那么为什么会有最左匹配原则这种说法也就理解了。

我们都知道索引的底层是一颗B+树,那么联合索引当然还是一颗B+树,只不过联合索引的健值数量不是一个,而是多个。构建一颗B+树只能根据一个值来构建,因此数据库依据联合索引最左的字段来构建B+树。

例子:假如创建一个(a,b)的联合索引,那么它的索引树是这样的


可以看到a的值是有顺序的,1,1,2,2,3,3,而b的值是没有顺序的1,2,1,4,1,2。所以b = 2这种查询条件没有办法利用索引,因为联合索引首先是按a排序的,b是无序的。

八、前缀索引

前缀索引就是针对字段的“前特定个字符”建立索引,而非对整个字段的值建立索引。

显然,因为没有对完整的字段值建立索引,所以这样建立的索引更小,查询更快。MySQL的前缀索引能有效减小索引文件的大小,提高索引的速度。

但是前缀索引也有它的坏处:MySQL 不能在 ORDER BY 或 GROUP BY 中使用前缀索引,也不能把它们用作覆盖索引(Covering Index)。
可以通过下面的预发建立前缀索引:

ALTER TABLE table_name ADD KEY(column_name(prefix_length));

九、覆盖索引

覆盖索引(covering index)指一个查询语句的执行只需要从辅助索引中就可以得到查询记录,而不需要查询聚集索引中的记录。也可以称之为实现了索引覆盖。辅助索引不包含一整行的记录,因此可以大大减少IO操作。覆盖索引是mysql dba常用的一种SQL优化手段。

十、回表与索引覆盖

比如 InNoDB 数据库引擎,采用的是聚集索引+ 非聚集索引,其中主键索引是聚集索引,辅助索引是非聚集索引,所以查询时需要先通过辅助索引找到主键,然后再通过主键索引最终找到数据,这个过程就是回表查询。

所以性能相对于只扫描一遍的聚集索引树的性能要低一些

所以需要对回表查询优化,也就引入了索引覆盖,具体的做法就是将要查询的数据作为索引列建立普通索引(可以是单列索引,也可以一个索引语句定义所有要查询的列,即联合索引),这样的话就可以直接返回索引中的的数据,不需要再通过聚集索引去定位行记录,避免了回表的情况发生。

要注意的是,不是所有类型的索引都可以成为覆盖索引的。因为覆盖索引必须要存储索引的列值,而哈希索引、空间索引和全文索引等都不存储索引列值,索引MySQL只能使用B-Tree索引做覆盖索引。

覆盖索引的优点:

(1)索引条目通常远小于数据行的大小,因为覆盖索引只需要读取索引,极大地减少了数据的访问量
(2)索引是按照列值顺序存储的,对于IO密集的范围查找会比随机从磁盘读取每一行数据的IO小很多。
(3)由于InnoDB的聚簇索引,覆盖索引对InnoDB引擎下的数据库表特别有用。因为InnoDB的二级索引在叶子节点中保存了行的主键值,如果二级索引能够覆盖查询,就避免了对主键索引的二次查询。

十一、什么时候需要回表?什么时候不需要?

聚簇索引和覆盖索引不需要回表,其他情况都需要回表

十二、建立索引需要考虑的因素

(1)建立索引的时候一般要考虑到字段的使用频率,经常作为条件进行查询的字段比较适合。
(2)如果需要建立联合索引的话,还需要考虑联合索引中的顺序。
(3)此外也要考虑其他方面,比如防止过多的索引对表造成太大的压力。

【参考】

【1】https://segmentfault.com/a/1190000017387880
【2】https://blog.csdn.net/weixin_42181824/article/details/82261988

以上是关于带你整理面试过程中关于数据库的索引的相关知识点的主要内容,如果未能解决你的问题,请参考以下文章

带你整理面试过程中关于Innodb的相关知识点

带你整理面试过程中关于ARP 协议的相关知识点

带你整理面试过程中关于Redis 中的持久化的相关知识点

带你整理面试过程中关于消息队列MQ的相关知识

带你整理面试过程中关于Redis 中数据结构的相关知识点

带你整理面试过程中关于Redis 的删除策略的相关知识点