深入理解MySQL数据库索引

深入理解MySQL数据库索引

个人主页:顾漂亮

1. 索引简介

1.1 索引是什么?

MySQL的索引是一种数据结构它可以帮助数据库高效地查询、更新数据表中的数据。索引通过一定的规则排列数据表中的记录,使得对表的查询可以通过对索引的搜索来加快速度。

MySQL索引类似于书籍的目录,通过指向数据行的位置,**可以快速定位和访问表中的数据。**就像使用汉语字典的目录(索引)页,可以通过笔画、偏旁部首、拼音等排序的目录快速查到所需要的字。

1.2 为什么要使用索引?

  • 在工作或者学习中,如果对索引比较了解,可以写出更高效率的SQL语句
  • 索引可以提升数据检索的效率,在应用程序的运行过程中,查询操作的频率远远高于增删改的频率。

2. 索引需要采用什么样的数据结构?

2.1 Hash -- 哈希表

查询和删除数据的时间复杂度都是O(1),查询速度非常快,但MySQL并没有选择其作为索引的原因是Hash不支持范围查找。

2.2 二叉搜索树

落选原因:

  • 最坏情况下,查询时间复杂度为O(N)。
  • 节点个数过多无法保证数的高度。
  • AVL和红黑树虽然是平衡或者近似平衡,但毕竟是二叉树。
  • 由于数据都是在磁盘上保存的,在检索数据时,每次访问某个节点的子节点时都会发生一次磁盘IO,而在整个数据库系统中,IO是性能的瓶颈,减少IO次数可以有效提升性能。

2.3 N叉树

为了解决树高的问题,可以使用N叉树。

Tips:推荐一个数据结构可视化网站https://www.cs.usfca.edu/~galles/visualization/Algorithms.html

由上图可知,相同数据量的情况下,N叉树的树高可以得到有效控制,也就意味着在相同的情况下可以减少IO的次数,从而提升效率。但是MySQL认为N叉树作为索引的数据结构不是最理想的。

2.4 B+树

  • MySQL使用的是B+树的升级版 ,一般B+树的叶子节点可以组织成一个单链表,MySQL则将其升级为双向链表结构 ,更大提升查询和修改的效率。

  • B+树是一种经常用于数据库和文件系统等场合的平衡查找树 ,MySQL索引采用的数据结构,以4阶B+树为例:

B+树的特点:

  • 能够保持数据稳定有序 ,插入和修改有较为稳定的时间复杂度
  • 非叶子节点仅具有索引作用,不存储数据,所有叶子节点保存真实数据。
  • 所有的叶子节点构成一个有序链表。

B+树和B树的对比:

  • B+树的叶子节点中的数据是连续的,且相互链接,便于区间查找和搜索。
  • 非叶子节点的值都包含在叶子节点中。
  • 对于B+树而言,在和B树相同树高的情况下,查找任意元素的时间复杂度都为log(N),性能更加均衡。

3. MySQL中的页

3.1 为什么要使用页?

在.ibd文件中最重要的结构体就是Page(页),页是内存与磁盘交互的最小单元 ,默认大小为16KB,每次内存和磁盘的交互至少读取一页 ,所以在磁盘中每个页内部的地址都是连续的。根据局部性原理,将来要使用的数据大概率与当前访问的数据在空间上是邻近的,所以一次从磁盘中读取一页的数据放入内存中,当下次查询的数据还在这个页中就可以直接从内存中直接读取,从而减少磁盘IO,提高性能。

每一页中即使没有数据也会使用16KB的存储空间,同时与索引的B+树中的节点对应,查看页的大小,可以通过系统变量innodb_page_size查看:

sql 复制代码
mysql> show variables like 'innodb_page_size';
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| innodb_page_size | 16384 | # 16384表示的是字节数 16*1024 = 16384
+------------------+-------+
1 row in set, 6 warnings (0.01 sec)
  • 在MySQL中有多种不同类型的页,最常用的就是用来存储数据和索引的"索引页",也叫做"数据页",但是不论哪一种类型的页都会包含页头和页尾 ,页的主体信息使用数据行进行填充,数据页的基本结构如下图所示:

3.2 页文件头和页文件尾

  • 着重关注内容:上一个页号和下一页页号,通过这两个属性可以把页和页之间链接起来,形成一个双向链表。

3.3 页主体

  • 页主体部分是保存真实数据的主要区域,每当创建一个新页时,都会自动分配两个行,一个是页内最小行Infimun,另一个是页内最大行Supremun,这两个行并不存储任何真实信息,而是作为数据行链表的头和尾。第一个数据行有一个记录下一行的地址偏移量的区域next_record,将页内所有数据行组成了一个单向链表。

  • 当向一个新页插入数据时,将Infimun连接第一个数据行,最后一行真实数据行连接Supremun,通过这样的方式,数据行就构成了一个单向链表的结构,更多的数据插入之后,会按照主键从小到大的顺序进行连接。

3.4 页目录

  • 当按照主键或者索引查找某条数据的时候,最简单直接的方案是从最小行Infimun开始,沿着链表顺序逐个比对查找,但一个页有16KB,通常会存在上百行数据,每次都要遍历数百行,无法满足高效查询。为了提高查询效率,InnoDB采用二分查找解决上述问题。

  • 具体实现方式是在每一个页加入一个页目录,将页内包含最小、最大行在内的所有行进行分组,约定头行单独为一组,其他每个组最多8条数据,同时把每一个组最后一行在页中的地址,按照主键从小到大记录的顺序记录在页目录中,页目录中每一个位置称为一个槽,每个槽对应一个分组。

  • 后续查找按照先在页目录中查找到对应的槽,再进行遍历槽中的数据,最终得到想要的结果。

3.5 数据页头

数据页头记录当前页保存数据相关的信息。

4. B+树在MySQL索引中的应用

查找id=5的过程:加载索引页1 -> 加载索引页2 -> 加载数据页3。

注意点:

  • 因为索引页数据量相对较小的情况下,如果可以把索引页缓存到内存中一次IO就可以找到目标数据,提升查询性能。

5. 索引分类

5.1 主键索引

  • 当在一个表上定义一个主键时,InnoDB使用其作为聚集索引。

  • 推荐:为每一个表定义一个主键,如果没有逻辑上唯一且非空的列或者列集可以使用主键,则添加一个自增列。

创建主键索引:

sql 复制代码
-- 方式一:创建表时创建主键
create table t_test_pk(
  id bigint primary key auto_increment,
  name varchar(20)
);

-- 方式二:创建表时单独指定主键列
create table t_test_pk1(
  id bigint auto_increment,
  name varchar(20),
  primary key(id)
);

-- 方式三:修改表中的列为主键索引
create table t_test_pk2(
  id bigint,
  name varchar(20)
);

alter table t_test_pk2 add primary key(id);
alter table t_test_pk2 modify id bigint auto_increment;

5.2 普通索引

  • 最基本的索引类型,没有唯一性的限制。

  • 可以为多个列创建组合索引,称为复合索引。

  • 为了提升查询效率,可以为查询频繁的列创建普通索引。

普通索引的使用场景:

  • 创建表的时候,明确知道某些列是频繁查询的列,就直接创建。
  • 在工作中,一般表中数据量很大的时候,可以创建普通索引来提升查询效率。
  • 若数据量非常小,有时全表扫描的效率或许会比创建普通索引效率还要高,因此需要在合适情况下使用。

创建普通索引:

sql 复制代码
-- 创建表时指定索引列
create table t_test_index(
  id bigint primary key auto_increment,
  name varchar(20) unique,
  sno varchar(20),
  index(sno)
);

-- 修改表中的列为普通索引
create table t_test_index1(
  id bigint primary key auto_increment,
    name varchar(20),
  sno varchar(20)
);
alter table t_test_index1 add index(sno);

-- 单独创建索引并指定索引名
create table t_test_index2(
  id bigint primary key auto_increment,
  name varchar(20),
  sno varchar(20)
);

create index index_name on t_test_index2(sno);	

5.3 唯一索引

  • 在一个表上定义一个唯一键unique时,自动创建唯一索引。
  • 与普通索引类似,但区别在于唯一索引的列不允许有重复值。

创建唯一索引:

sql 复制代码
-- 创建表时创建唯一索引
create table t_test_uk(
  id bigint primary key auto_increment,
  name varchar(20) unique
);

-- 创建表时单独指定
create table t_test_uk1(
  id bigint primary key auto_increment,
  name varchar(20),
  unique(name)
);

-- 修改表中的列为唯一索引
create table t_test_uk2(
  id bigint primary key auto_increment,
  name varchar(20)
);
alter table t_test_uk2 add unique(name);

5.4 全文索引

  • 基于文本列(CHARVARCHARTEXT)上创建,以加快对这些列中包含的数据查询和DML操作。可以用于全文搜索,仅MyISAMInnoDB支持。

5.5 聚集索引

  • 与主键索引是同义词。
  • 如果没有为表定义主键,InnoDB使用第一个uniquenot null的列作为聚集索引。
  • 如果表中没有主键或者合适的unique索引,InnoDB会为新插入的行生成一个行号并且用6字节的ROW_ID字段记录,ROW-ID单调递增,并使用ROW-ID作为索引。

5.6 非聚集索引

  • 聚集索引以外的索引称为非聚集索引或二级索引。
  • 非聚集索引中的每一条数据都包含该行的主键值,以及非聚集索引指定的列。
  • InnoDB使用这个主键值来搜索聚集索引中的行,这个过程称为回表查询

详细解释回表查询:

  • 回表查询是MySQL中的一种查询方式,主要在使用二级索引(非聚簇索引)时发生。具体过程如下:
  1. 二级索引查找:首先通过二级索引找到对应的主键值。
  2. 回表操作:根据主键值回到聚簇索引中查找完整的行数据。
示例

假设有一张表users,结构如下:

sql 复制代码
CREATE TABLE users (
    id INT PRIMARY KEY,
    name VARCHAR(100),
    age INT,
    INDEX idx_age (age)
);
执行查询:
sql 复制代码
SELECT * FROM users WHERE age = 25;
  • 使用二级索引:先在idx_age索引中找到age = 25的主键值。
  • 回表:根据主键值回到聚簇索引中获取完整的行数据。
性能影响
  • 优点:二级索引占用空间小,查找速度快。
  • 缺点:回表操作增加额外I/O,可能影响性能。
优化方法
  • 覆盖索引:确保查询字段都在索引中,避免回表。
  • 索引优化:合理设计索引,减少回表次数。

5.7 索引覆盖

  • 当需要查询的列包含在创建的索引列中,可以直接从普通索引返回,这个现象叫做索引覆盖

举例:

sql 复制代码
SELECT age FROM users WHERE age = 25;

上述代码会发生索引覆盖,这样的查询方式能够减少内存和磁盘IO,提升查找效率。

6. 查看索引

sql 复制代码
show index from t_test_index;

7. 删除索引

删除具有自增属性的主键

sql 复制代码
-- 直接删除含有自增属性的主键索引的列,会报错
alter table t_test_index drop primary key;

-- 错误信息 1075 - Incorrect table definition; there can be only one auto column and it must be defined as a key

-- 先删除自增属性
alter table t_test_index modify id bigint;

删除其他索引

sql 复制代码
-- 直接删除其他索引
alter table t_test_uk1 drop index name;
-- name为表中索引名称

8. 总结

创建索引的注意事项:

  • 索引应该创建在高频查询的列上。
  • 索引需要占用额外的存储空间 -- 每一个索引都会生成对应的一个索引树。
  • 对表进行插入、更新和删除操作的时候,同时也会修改索引,可能会影响性能 -- 因为要维护好一个索引树。
  • 创建过多或不合理的索引会导致性能下降,需要谨慎选择和规划索引

相关推荐
极限实验室2 分钟前
INFINI Labs 产品更新 - Coco AI – 增强 AI 搜索、API 管理与性能优化等
数据库·人工智能
一线大码1 小时前
关于 LEFT JOIN 的使用注意事项
后端·sql·mysql
Y_3_71 小时前
FlinkCDC 达梦数据库实时同步详解
数据库
uesowys1 小时前
腾讯云MySQL数据库架构分析与使用场景
mysql·腾讯云·数据库架构
chat2tomorrow1 小时前
QuickAPI:一键将 Excel 数据转为数据库表
数据库·sql·mysql·oracle·excel·统一数据服务·sql2api
今天也想快点毕业1 小时前
简单创建一个Django项目并配置neo4j数据库
数据库·django·neo4j
StevenLdh2 小时前
浅谈StarRocks数据库简介及应用
数据库
机器视觉—ing2 小时前
C# 不同框架如何调用framework 和 net core
java·数据库·c#
仙魁XAN2 小时前
Flutter 学习之旅 之 flutter 使用 SQLite(sqflite) 实现简单的数据本地化 保存/获取/移除/判断是否存在 的简单封装
数据库·flutter·sqlite·sqflite·本地化数据
珹洺3 小时前
计算机网络:(二)计算机网络在我国发展与网络类别与性能 (附带图谱更好对比理解)
运维·服务器·网络·数据库·后端·计算机网络