MySQL索引

本文深入探讨了MySQL中索引的重要性和优缺点,强调了B树和B+树在索引结构中的应用。B树和B+树通过减少磁盘IO次数来提高查询效率,B+树尤其适合范围查询。在InnoDB中,主键索引采用B+树结构,数据行与索引存储在一起,而MyISAM则将数据和索引分开存储。创建索引应考虑查询频率、数据唯一性等因素,避免对频繁更新或重复值多的列建立索引。合适的索引策略能极大提升数据库性能。
摘要由CSDN通过智能技术生成

索引是对数据库表中一列或多列的值进行排序的一种结构。MySQL索引的建立对于MySQL的高效运行是很重要的,索引可以大大提高MySQL的检索速度。索引只是提高效率的一个因素,如果你的MySQL有大数据量的表,就需要花时间研究建立最优秀的索引,或优化查询语句。

1. 索引的优缺点

索引可以大大提高MySQL的检索速度,为什么不对表中的每一个列创建一个索引呢?

1. 优点

  • 索引大大减小了服务器需要扫描的数据量,从而大大加快数据的检索速度,这也是创建索引的最主要的原因。
  • 索引可以帮助服务器避免排序和创建临时表
  • 索引可以将随机IO变成顺序IO
  • 索引对于InnoDB(对索引支持行级锁)非常重要,因为它可以让查询锁更少的元组,提高了表访问并发性
  • 关于InnoDB、索引和锁:InnoDB在二级索引上使用共享锁(读锁),但访问主键索引需要排他锁(写锁)
  • 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。
  • 可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。
  • 在使用分组和排序子句进行数据检索时,同样可以显著减少查询中分组和排序的时间。
  • 通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。

2. 缺点

  • 创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加
  • 索引需要占物理空间,除了数据表占用数据空间之外,每一个索引还要占用一定的物理空间,如果需要建立聚簇索引,那么需要占用的空间会更大
  • 对表中的数据进行增、删、改的时候,索引也要动态的维护,这就降低了整数的维护速度
  • 如果某个数据列包含许多重复的内容,为它建立索引就没有太大的实际效果。
  • 对于非常小的表,大部分情况下简单的全表扫描更高效;

2. 创建索引准则

索引是建立在数据库表中的某些列的上面。因此,在创建索引的时候,应该仔细考虑在哪些列上可以创建索引,在哪些列上不能创建索引。

1. 应该创建索引的列

  • 在经常需要搜索的列上,可以加快搜索的速度
  • 在作为主键的列上,强制该列的唯一性和组织表中数据的排列结构
  • 在经常用在连接(JOIN)的列上,这些列主要是一外键,可以加快连接的速度
  • 在经常需要根据范围(<,<=,=,>,>=,BETWEEN,IN)进行搜索的列上创建索引,因为索引已经排序,其指定的范围是连续的
  • 在经常需要排序(order by)的列上创建索引,因为索引已经排序,这样查询可以利用索引的排序,加快排序查询时间
  • 在经常使用在WHERE子句中的列上面创建索引,加快条件的判断速度

2. 不该创建索引的列

  • 对于那些在查询中很少使用或者参考的列不应该创建索引。
    若列很少使用到,因此有索引或者无索引,并不能提高查询速度。相反,由于增加了索引,反而降低了系统的维护速度和增大了空间需求。
  • 对于那些只有很少数据值或者重复值多的列也不应该增加索引。
    这些列的取值很少,例如人事表的性别列,在查询的结果中,结果集的数据行占了表中数据行的很大比例,即需要在表中搜索的数据行的比例很大。增加索引,并不能明显加快检索速度。
  • 对于那些定义为text, image和bit数据类型的列不应该增加索引。
    这些列的数据量要么相当大,要么取值很少。
    当该列修改性能要求远远高于检索性能时,不应该创建索引。(修改性能和检索性能是互相矛盾的)

3. 索引底层数据结构

1. B树

B树的优点

使用B树与B+树解决了平衡二叉树的这两个缺点:

  1. MySQL的数据是存储在磁盘文件中的,查询处理数据时,需要先把磁盘中的数据加载到内存中,磁盘IO操作非常耗时,所以我们优化的重点就是尽量减少磁盘IO操作,每次访问树的节点都会发生一次IO,如果想要减少磁盘IO操作,就需要减少对节点的访问,也就是降低树的高度。

一个简单的想法是在每个节点存储更多的数据,增加树的叉数,将树从高瘦变为矮胖。比如每个节点存储1000个索引,构建1百万条数据,树的高度只需要2层就可以(1000*1000=1百万),也就是说只需要2次磁盘IO就可以查询到数据。磁盘IO次数变少了,查询数据的效率也就提高了。

  1. 平衡二叉树范围查询时需要从根节点多次遍历,查询效率不高——由B+树解决。

B树的结构

  1. B树的节点中存储着多个元素,每个内节点有多个分叉。

  2. 节点中的元素包含键值和数据,节点中的键值从大到小排列。也就是说,在所有的节点都储存数据。

  3. 父节点当中的元素不会出现在子节点中。

  4. 所有的叶子结点都位于同一层,叶节点具有相同的深度,叶节点之间没有指针连接。

在这里插入图片描述
假如我们查询值等于10的数据。查询路径磁盘块1->磁盘块2->磁盘块5。

第一次磁盘IO:将磁盘块1加载到内存中,在内存中从头遍历比较,10<15,走P1指针,到磁盘寻址磁盘块2。

第二次磁盘IO:将磁盘块2加载到内存中,在内存中从头遍历比较,7<10<12,到磁盘中寻址定位到磁盘块6。

第三次磁盘IO:将磁盘块6加载到内存中,在内存中从头遍历比较,10=10,找到10,取出data,如果data存储的行记录,取出data,查询结束。如果存储的是磁盘地址,还需要根据磁盘地址到磁盘中取出数据,查询终止。

相比二叉平衡查找树,在整个查找过程中,虽然数据的比较次数并没有明显减少,但是磁盘IO次数会大大减少。同时,由于我们的比较是在内存中进行的,比较的耗时可以忽略不计,B树的高度一般2至3层就能满足大部分的应用场景,所以使用B树构建索引可以很好的提升查询的效率。

B树的缺点

  1. B树不支持范围查询的快速查找,你想想这么一个情况如果我们想要查找10和35之间的数据,查找到15之后,需要回到根节点重新遍历查找,需要从根节点进行多次遍历,查询效率有待提高。

  2. 如果data存储的是行记录,行的大小随着列数的增多,所占空间会变大。这时,一个页中可存储的数据量就会变少,树相应就会变高,磁盘IO次数就会变大。

2. B+树

与B树的区别

B树:非叶子节点和叶子节点都会存储数据。
B+树:只有叶子节点才会存储数据,非叶子节点只存储键值。叶子节点之间使用双向指针连接,最底层的叶子节点形成了一个双向有序链表。

B+树结构

在这里插入图片描述
B+树的最底层叶子节点包含了所有的索引项,从图上可以看到,B+树在查找数据的时候,由于数据都存放在最底层的叶子节点上,所以每次查找都需要检索到叶子节点才能查询到数据。

所以在需要查询数据的情况下每次的磁盘的IO跟树高有直接的关系,但是从另一方面来说,由于数据都被放到了叶子节点,所以每层可以存放的节点数更多,B+树的树高理论上情况下是比B树要矮的。

等值查询:
假如我们查询值等于9的数据。查询路径磁盘块1->磁盘块2->磁盘块6。

第一次磁盘IO:将磁盘块1加载到内存中,在内存中从头遍历比较,9<15,走p1,到磁盘块2。

第二次磁盘IO:将磁盘块2加载到内存中,在内存中从头遍历比较,7<9<12,到磁盘中寻址定位到磁盘块6。

第三次磁盘IO:将磁盘块6加载到内存中,在内存中从头遍历比较,在第三个索引中找到9,取出data,如果data存储的行记录,取出data,查询结束。如果存储的是磁盘地址,还需要根据磁盘地址到磁盘中取出数据,查询终止。(这里需要区分的是在InnoDB中Data存储的为行数据,而MyIsam中存储的是磁盘地址

范围查询:
假如我们想要查找9和26之间的数据。查找路径是磁盘块1->磁盘块2->磁盘块6->磁盘块7。

首先查找值等于9的数据,将值等于9的数据缓存到结果集。这一步和前面等值查询流程一样,发生了三次磁盘IO。

底层的叶子节点是一个有序列表,我们从磁盘块6,键值9开始向后遍历筛选所有符合筛选条件的数据。

第四次磁盘IO:根据磁盘6后继指针到磁盘中寻址定位到磁盘块7,将磁盘7加载到内存中,在内存中从头遍历比较,将data缓存到结果集。

主键具备唯一性(后面不会有<=26的数据),不需再向后查找,查询终止。将结果集返回给用户。

可以看到B+树可以保证等值和范围查询的快速查找,MySQL的索引就采用了B+树的数据结构。

4. InnoDB实现索引

1. 主键索引(聚簇索引)

InnoDB的数据和索引存储在一个文件t_user_innodb.ibd中。

每个InnoDB表都有一个聚簇索引 ,聚簇索引使用B+树构建,叶子节点存储的数据是整行记录。一般情况下,聚簇索引等同于主键索引,一张表只能有一个主键索引,不允许重复、不允许为 NULL,当一个表没有创建主键索引时,InnoDB会自动创建一个ROWID字段来构建聚簇索引。

InnoDB创建索引的具体规则如下:

  1. 在表上定义主键PRIMARY KEY,InnoDB将主键索引用作聚簇索引。
  2. 如果表没有定义主键,InnoDB会选择第一个不为NULL的唯一索引列用作聚簇索引。
  3. 如果以上两个都没有,InnoDB 会使用一个6 字节长整型的隐式字段 ROWID字段构建聚簇索引。该ROWID字段会在插入新行时自动递增。
#创建主键=创建主键索引
ALTER TABLE TableName ADD PRIMARY KEY(column_list); 

这里以user_innodb为例,user_innodb的id列为主键,age列为普通索引。

CREATE TABLE `user_innodb`
(
  `id`       int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(20) DEFAULT NULL,
  `age`      int(11)     DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_age` (`age`) USING BTREE
) ENGINE = InnoDB;

在这里插入图片描述
InnoDB的数据组织方式,是聚簇索引。

主键索引的叶子节点会存储数据行,辅助索引只会存储主键值。

在这里插入图片描述

2. 非主键索引(辅助索引)

2.1 唯一索引:数据列不允许重复,允许为 NULL 值,一张表可有多个唯一索引,索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一。

创建方法:

CREATE UNIQUE INDEX IndexName ON `TableName`(`字段名`(length));
或者
ALTER TABLE TableName ADD UNIQUE (column_list); 

或:

#当给键盘设唯一性约束时,自动创建索引
字段名 数据类型 UNIQUE;

create table student04(
id int,
name varchar(20) unique
);

2.2 普通索引:一张表可以创建多个普通索引,一个普通索引可以包含多个字段,允许数据重复,允许 NULL 值插入;

创建方法:

CREATE INDEX IndexName ON `TableName`(`字段名`(length));

ALTER TABLE TableName ADD INDEX IndexName(`字段名`(length));

以表user_innodb的age列为例,age索引的索引结果如下图。
在这里插入图片描述
底层叶子节点的按照(age,id)的顺序排序,先按照age列从小到大排序,age列相同时按照id列从小到大排序。

使用辅助索引需要检索两遍索引:首先检索辅助索引获得主键,然后使用主键到主索引中检索获得记录。

唯一索引与普通索引的选择:
市民系统,每个人都有一个唯一的身份证号。 select name from CUser where id_card = ‘xxxxxxxyyyyyyzzzzz’; 由于身份证号字段比较大,不建议把身份证号当做主键,那么现在有两个选择,要么给 id_card字段创建唯一索引,要么创建一个普通索引。如果业务代码已经保证了不会写入重复的身份证号,那么这两个选择逻辑上都是正确的。

  • 对于普通索引来说,查找到满足条件的第一个记录后,需要查找下一个记录,直到碰到第一个不 满足条件的记录。

  • 对于唯一索引来说,由于索引定义了唯一性,查找到第一个满足条件的记录后,就会停止继续检索。

3. 二者的联系

例:有一个主键列为ID的表,表中有字段k,并且在k上有索引在这里插入图片描述
每一个索引在InnoDB里面对应一棵B+树。

主键查询方式:只需要搜索ID这棵B+树;
普通索引查询方式:需要先搜索k索引树,得到ID的值,再到ID索引树搜索数据。这个过程称为回表

执行 select *fromTwhere k between 3 and 5过程如下:

  1. 在k索引树上找到k=3的记录,取得 ID = 300;
  2. 再到ID索引树查到ID=300对应的R3;
  3. 在k索引树取下一个值k=5,取得ID=500;
  4. 再回到ID索引树查到ID=500对应的R4;
  5. 在k索引树取下一个值k=6,不满足条件,循环结束。

回到主键索引树搜索的过程,我们称为回表,由于查询结果所需要的数据只在主键索引上有,所以不得不回表。

4. 查询过程

还是以此表为例:
在这里插入图片描述
查询的值为主键:

select * from user_innodb where id = 28;

在这里插入图片描述

  1. 先在主键树中从根节点开始检索,将根节点加载到内存,比较28<75,走左路。(1次磁盘IO)

  2. 将左子树节点加载到内存中,比较16<28<47,向下检索。(1次磁盘IO)

  3. 检索到叶节点,将节点加载到内存中遍历,比较16<28,18<28,28=28。查找到值等于28的索引项,直接可以获取整行数据。将改记录返回给客户端。(1次磁盘IO)

磁盘IO数量:3次。

查询的值非主键:

select * from t_user_innodb where age=19;

在这里插入图片描述
根据在辅助索引树中获取的主键id,到主键索引树检索数据的过程称为回表查询。

磁盘IO数:辅助索引3次+获取记录回表3次。

5. 组合索引

例:

CREATE TABLE `abc_innodb`
(
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `a`  int(11)     DEFAULT NULL,
  `b`  int(11)     DEFAULT NULL,
  `c`  varchar(10) DEFAULT NULL,
  `d`  varchar(10) DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_abc` (`a`, `b`, `c`)
) ENGINE = InnoDB;

最后一行为8 14 14 14

在这里插入图片描述
查询过程:

select * from abc_innodb where a = 13 and b = 16 and c = 4;

在这里插入图片描述

6. 覆盖索引

如果执行的语句是 select ID from T where k between 3 and 5 ,这时只需要查ID的值,而ID的值已经在k索引树上了,因此可以直接提供查询结果,不需要回表。

由于覆盖索引可以减少树的搜索次数,显著提升查询性能,所以使用覆盖索引是一个常用的性能优化手段。

eg:如果有根据身份证号查询市民信息的需求,一般我们只要在身份证号字段上建立索引就够了。但是如果现在有一个高频请求,要根据市民的身份证号查询他的姓名,就可以设置联合索引(身份证号、姓名)。它可以在这个高频请求上用到覆盖索引,不再需要回表查整行记录,减少语句的执行时间。

7. 最左前缀原则

当b+树的数据项是复合的数据结构,比如(name,age,sex)的时候,b+数是按照从左到右的顺序来建立搜索树的。

比如当(张三,20,F)这样的数据来检索的时候,b+树会优先比较name来确定下一步的所搜方向,如果name相同再依次比较age和sex,最后得到检索的数据;但当(20,F)这样的没有name的数据来的时候,b+树就不知道下一步该查哪个节点,因为建立搜索树的时候name就是第一个比较因子,必须要先根据name来搜索才能知道下一步去哪里查询。比如当(张三,F)这样的数据来检索时,b+树可以用name来指定搜索方向,但下一个字段age的缺失,所以只能把名字等于张三的数据都找到,然后再匹配性别是F的数据了, 这个是非常重要的性质,即索引的最左匹配特性。

8. 关于聚簇索引与非聚簇索引

聚簇是为了提高某个属性(或属性组)的查询速度,把这个或这些属性(称为聚簇码)上具有相同值的元组集中存放在连续的物理块。

聚簇索引(clustered index)不是单独的一种索引类型,而是一种数据存储方式。这种存储方式是依靠B+树来实现的,根据表的主键构造一棵B+树且B+树叶子节点存放的都是表的行记录数据时,方可称该主键索引为聚簇索引。聚簇索引也可理解为将数据存储与索引放到了一块,找到索引也就找到了数据。

非聚簇索引:数据和索引是分开的,B+树叶子节点存放的不是数据表的行记录。

虽然InnoDB和MyISAM存储引擎都默认使用B+树结构存储索引,但是只有InnoDB的主键索引才是聚簇索引,InnoDB中的辅助索引以及MyISAM使用的都是非聚簇索引。每张表最多只能拥有一个聚簇索引。

聚簇索引优缺点

优点:

数据访问更快,因为聚簇索引将索引和数据保存在同一个B+树中,因此从聚簇索引中获取数据比非聚簇索引更快
聚簇索引对于主键的排序查找和范围查找速度非常快

缺点:

插入速度严重依赖于插入顺序,按照主键的顺序插入是最快的方式,否则将会出现页分裂,严重影响性能。因此,对于InnoDB表,我们一般都会定义一个自增的ID列为主键(主键列不要选没有意义的自增列,选经常查询的条件列才好,不然无法体现其主键索引性能)更新主键的代价很高,因为将会导致被更新的行移动。因此,对于InnoDB表,我们一般定义主键为不可更新。
二级索引访问需要两次索引查找,第一次找到主键值,第二次根据主键值找到行数据。

5. MyIsam实现索引

1. 主键索引

CREATE TABLE `user`
(
  `id`       int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(20) DEFAULT NULL,
  `age`      int(11)     DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_age` (`age`) USING BTREE
) ENGINE = MyISAM
  AUTO_INCREMENT = 1
  DEFAULT CHARSET = utf8;

在这里插入图片描述

在这里插入图片描述

MyISAM使用B+树构建索引树时,叶子节点中存储的键值为索引列的值,数据为索引所在行的磁盘地址。

MyISAM的数据文件和索引文件是分开存储的,表user的索引存储在索引文件user.MYI中,数据文件存储在数据文件 user.MYD中。

2. 辅助索引

在 MyISAM 中,辅助索引和主键索引的结构是一样的,没有任何区别,叶子节点的数据存储的都是行记录的磁盘地址。只是主键索引的键值是唯一的,而辅助索引的键值可以重复。

查询数据时,由于辅助索引的键值不唯一,可能存在多个拥有相同的记录,所以即使是等值查询,也需要按照范围查询的方式在辅助索引树中检索数据。

3. 单点查询过程

select * from user where id = 28;

在这里插入图片描述

  1. 先在主键树中从根节点开始检索,将根节点加载到内存,比较28<75,走左路。(1次磁盘IO)
  2. 将左子树节点加载到内存中,比较16<28<47,向下检索。(1次磁盘IO)
  3. 检索到叶节点,将节点加载到内存中遍历,比较16<28,18<28,28=28。查找到值等于30的索引项。(1次磁盘IO)
  4. 从索引项中获取磁盘地址,然后到数据文件user.MYD中获取对应整行记录。(1次磁盘IO)
  5. 将记录返给客户端。

共4次磁盘IO次数:3次索引检索+记录数据检索。

4. 范围查询过程

select * from user where id between 28 and 47;

在这里插入图片描述

  1. 先在主键树中从根节点开始检索,将根节点加载到内存,比较28<75,走左路。(1次磁盘IO)
  2. 将左子树节点加载到内存中,比较16<28<47,向下检索。(1次磁盘IO)
  3. 检索到叶节点,将节点加载到内存中遍历比较16<28,18<28,28=28<47。查找到值等于28的索引项。
  4. 根据磁盘地址从数据文件中获取行记录缓存到结果集中。(1次磁盘IO)
  5. 我们的查询语句时范围查找,需要向后遍历底层叶子链表,直至到达最后一个不满足筛选条件。
  6. 向后遍历底层叶子链表,将下一个节点加载到内存中,遍历比较,28<47=47,根据磁盘地址从数据文件中获取行记录缓存到结果集中。(1次磁盘IO)

最后得到两条符合筛选条件,将查询结果集返给客户端。

共5次磁盘IO:4次索引检索+记录数据检索。

需要注意的是:以上分析仅供参考,MyISAM在查询时,会将索引节点缓存在MySQL缓存中,而数据缓存依赖于操作系统自身的缓存,所以并不是每次都是走的磁盘,这里只是为了分析索引的使用过程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值