MySQL 索引:原理篇

本文首发于公众号:JavaArchJourney

索引优缺点

索引的优点:

  • 加速数据检索:索引可以大大加快数据的检索速度,特别是对于大型数据库表来说。通过创建索引,查询操作可以快速定位到所需的数据行,而不需要扫描整个表。
  • 提高排序和分组效率:当对数据进行 ORDER BY 或 GROUP BY 操作时,如果相应的列上有索引,那么这些操作会变得更快,因为索引本身就以一种有序的方式存储数据。
  • **行级锁的支持:**InnoDB 存储引擎通过索引来实现行级锁,确保只有满足查询条件的特定行被锁定,从而最小化锁定范围并提高并发处理能力。
  • 唯一性约束:使用唯一索引( UNIQUE )可以确保某列或某些列组合的值是唯一的。
  • 支持外键约束:在关系型数据库设计中,外键约束通常需要有索引来保证关联表之间的参照完整性。
  • 全文搜索优化:全文索引(类似于搜索引擎的倒排索引)特别适合用于在文本字段中进行复杂的搜索操作,提高搜索效率。

索引的缺点:

  • 增加存储空间需求:每个索引都需要额外的存储空间来保存其结构,尤其是对于大数据量的表来说,索引可能会占用大量的磁盘空间。
  • 降低写入性能:插入、更新或删除数据时,不仅需要修改表本身的数据,还需要相应地更新所有相关的索引。这意味着更多的磁盘 I/O 操作,从而影响写入性能。
  • 维护成本:随着数据的增删改,索引也需要不断地被维护,例如重建索引以保持其高效性,需要消耗一定的系统资源。
  • 潜在的锁定问题:在高并发环境下,索引更新可能导致更严重的锁定问题,进而影响应用的响应时间。
  • 复杂性的增加:过多的索引会使数据库管理系统变得更加复杂,同时也增加了管理的难度。不恰当的索引可能导致性能问题而不是提升性能。

索引存储原理

B-tree 索引

使用 B+ 树索引的优势

  • B+ 树的内部节点仅包含键值和指向子节点的指针,不存储实际数据行的信息;叶子节点存储了键值和实际的数据记录信息,并且所有叶子节点通过双向链表连接在一起,方便进行范围检索和顺序访问。一旦定位到起始点,就可以沿着叶子节点的链表快速地进行遍历。因此,B+ 树可以对 <,<=,=,>,>=,BETWEEN,IN,以及不以通配符开始的 LIKE 使用索引。
  • 要在磁盘上索引一个记录时,每次都要把节点数据读入内存中才能作比较判断;相比起磁盘 IO 耗时,在内存中对比数据的过程所花的时间基本可以忽略不计,所以需要尽量减少磁盘 IO 次数。由于磁盘 IO 是非常昂贵的操作,所以计算机操作系统对此做了优化------预读。因为局部预读原理说明:当访问一个地址数据的时候,与其相邻的数据很快也会被访问到,所以每次磁盘 IO 时,操作系统不仅仅把当前磁盘地址的数据加载到内存,同时也把相邻数据也加载到内存缓冲区中。每次磁盘 IO 读取的数据我们称之为一页(page),一页的大小与存储引擎有关,比如 InnoDB 为 16 KB。这也就意味着读取一页内数据的时候,实际上发生了一次磁盘 IO 。对应到 B+ 树中,每个节点其实就是一个 Page,数据以 Page 为单位在内存和磁盘间进行调度,也就是说,B+ 树每访问一个节点,就是进行一次磁盘 IO。因此,减少磁盘 IO 的次数就是要压缩树的高度,让瘦高的树尽量变成矮胖的树。每个 Page 的大小决定了相应结点的分支数量,由于 B+ 树的内部节点仅包含键值和指向子节点的指针而不直接存储数据,同样的空间可以容纳更多的键值。在给定大小的页中,B+ 树能够存储更多的索引项,从而减少了树的高度,降低了查找特定记录所需的磁盘 I/O 次数。由于 B+ 树的高度相对较低(通常是 3 到 4 层),即使对于非常大的数据集,也能保证快速定位到目标数据行。
  • B+ 树是一种自平衡树,所有叶子节点都在同一层级上,确保了从根节点到任何叶子节点的距离相等,从而保证了查询效率的一致性。

InnoDB 索引结构

InnoDB 使用 B+ 树 作为索引数据结构,并且默认使用聚簇索引来组织数据。

聚簇索引 :(primary 索引

  • 对于聚簇索引,内部节点页只包含了索引列,而叶子节点页包含了数据行的全部数据,所以聚簇索引"就是表"。聚簇索引将数据行按索引顺序物理地存储在磁盘上,所以一张表只能有一个聚簇索引。聚簇索引保证关键字的值相近的数据存储的物理位置也相近,所以字符串类型不宜建立聚簇索引,特别是随机字符串,会使得系统进行大量的移动操作。
  • 在 InnoDB 中,默认情况下主键列作为聚簇索引。

二级索引:(second 索引 / 辅助索引)

  • 除了聚簇索引之外的所有索引都是非聚簇索引。这些索引中的叶子节点包含的是主键值而不是直接指向数据行的指针。
  • 当通过二级索引来查找数据时,首先找到对应的主键值,然后使用这个主键值到聚簇索引中查找实际的数据行,这被称为回表查询。

MyISAM 索引结构

MyISAM 不支持聚簇索引。在 MyISAM 中,数据表的数据和索引是分开存储的。数据文件通常以 .MYD 扩展名保存,而索引文件则以 .MYI 扩展名保存。索引文件(.MYI)存储的是 B+ 树结构,叶子节点保存的是指向数据文件(.MYD)的物理地址。因此,每次通过索引访问数据时,都需要进行两次查找:一次是找到索引本身,另一次是根据索引找到对应的数据行。

  • 数据存储 :数据在 .MYD 文件中是按行顺序存储的。每一行的数据按照创建表时指定的列顺序依次写入。对于变长数据类型(如 VARCHAR, BLOB, TEXT),MyISAM 会在每个记录中为这些字段存储长度信息,以便能够正确读取数据。因为每行的大小是固定或已知的,所以 MyISAM 可以很容易地找到某一行数据的位置。
  • 索引存储:关于 MyISAM 是使用 B 树还是 B+ 树存储索引,目前网上好像没有找到确切的信息。B 树由于内部节点也存储数据,因此会比 B+ 树更加瘦高(不需要每次都遍历到叶子节点),但 B 树节点没有顺序链接特性(不方便遍历),因此 B 树主要适用于那些查询单个数据记录远比遍历数据更加常见引擎。但在实际使用中,数据库的遍历是常见操作,因此本文倾向于认为 MyISAM 的索引也是使用 B+ 树。

组合索引结构

组合索引,也称为复合索引或联合索引,是指在数据库表中为多个列创建的一个索引。

组合索引存储结构:

当我们为表中的若干列创建一个组合索引时,实际上是在这些列上构建了一个 B+ 树。

基于 B+ 树的多列键组织方式:

  • 非叶子节点:存储完整的复合索引键值以及指向子节点的指针。
  • 叶子节点:存储完整的复合索引键值以及对应的数据行主键值(二级索引,用于回表查询)。

多列键的排序规则:

  • 全局有序 :所有节点按组合索引的第一个(最左)列排序;如果第一列值相等,则按第二列排序,依此类推。例如,对于组合索引 (col1, col2)(col1=1, col2=9) (col1=9, col2=1) 之前。
  • 局部有序:在同一层级中,每个节点内的键值有序,支持高效的范围查询。

举个例子:

创建一个 students 表,并为该表创建组合索引(grade, last_name, first_name)

sql 复制代码
CREATE TABLE students (
  id INT PRIMARY KEY,
  grade INT,
  last_name VARCHAR(50),
  first_name VARCHAR(50)
);
CREATE INDEX idx_grade_lastname_firstname 
ON students (grade, last_name, first_name);

students 表中 8 个按组合索引 (grade, last_name, first_name) 顺序排列的示例数据:

id grade last_name first_name
1 10 Doe Alice
2 10 Smith Bob
3 10 Smith Charlie
4 11 Adams David
5 11 Adams Eve
6 11 Johnson Frank
7 12 Lee Grace
8 12 Lee Henry

B+ 树组合索引存储结构:

查询时的索引匹配规则:

最左匹配原则:查询条件必须使用组合索引的最左列,才能有效利用索引。

例如,组合索引 (col1, col2, col3) 可以支持以下查询:

  • WHERE col1 = ?
  • WHERE col1 = ? AND col2 = ?
  • WHERE col1 = ? AND col2 = ? AND col3 = ?

但无法支持以下查询:(除非数据库引擎支持某些高级特性,如索引跳跃扫描)

  • 未从索引的最左边的列开始使用:WHERE col2 = ?WHERE col3 = ?WHERE col2 = ? AND col3 = ?
  • 跳过中间某一索引列:WHERE col1 = ? AND col3 = ?。因为索引中 col3 的值是与 col2 关联的,若中间列col2 未指定,会导致无法确定 col3 的具体范围,这样数据库需要遍历所有 col1 = ? 的记录,再在这些记录中筛选 col3 = ?,这相当于全索引扫描 + 过滤。

为什么需要组合索引?:

  • 多列组合查询 :借助组合索引的有序性,一次索引查找便可高效支持多列组合的条件与范围查询(如 WHERE col1 > 100 AND col2 = 200)。
  • 减少回表次数:如果查询的列都包含在组合索引中(覆盖索引),可以直接从索引获取数据,无需回表。

其他索引

哈希索引(Hash Index):

  • 一种特殊类型的索引,使用哈希表来实现。哈希索引为每一行计算一个哈希值,这个哈希值是基于索引列的内容计算出来的,并且指向数据行的实际位置。
  • 哈希索引主要用于等值查询(例如 =, IN()),对于排序、范围/模糊查询(如 <, >, BETWEEN, LIKE 等)支持不好。
  • 在 MySQL 中,只有 Memory 存储引擎显式支持哈希索引。

空间索引(Spatial Index):

  • 空间索引主要用于提高对空间数据类型(如点、线、多边形等)的查询效率。空间索引在处理地理信息系统(GIS)相关操作时特别有用,比如查找位于特定区域内的所有对象或者计算两个几何形状之间的距离等。
  • MySQL 使用 R-Tree 作为空间索引的数据结构。R-Tree 是一种平衡树,专为多维空间数据设计,可以有效地支持范围查询和最近邻查询。与 B-Tree 不同,B-Tree 主要用于一维数据(例如数字或字符串),而 R-Tree 可以处理多维空间中的几何对象。

全文索引(Full-Text Index):

  • 全文索引主要用于加速对文本字段中包含的自然语言内容进行复杂查询,如搜索特定单词或短语。它特别适用于需要在大文本字段中执行快速搜索的场景。
  • MySQL 的全文索引基于倒排索引(Inverted Index)机制实现,类似于搜索引擎。倒排索引是一种数据结构,它存储了词项到文档列表的映射关系,即对于每个词项都记录了哪些行(或文档)包含了这个词项。倒排索引在构建时,会扫描文档并为每个词项创建一个指向包含该词项的所有文档的列表,然后在查询时就可以通过查找特定词项直接获取相关文档列表,从而实现快速检索。

参考

相关推荐
橙子家2 小时前
接口 IResultFilter、IAsyncResultFilter 的简介和用法示例(.net)
后端
CYRUS_STUDIO2 小时前
一步步带你移植 FART 到 Android 10,实现自动化脱壳
android·java·逆向
bobz9652 小时前
Virtio-networking: 2019 总结 2020展望
后端
AntBlack2 小时前
每周学点 AI : 在 Modal 上面搭建一下大模型应用
后端
2301_803554522 小时前
mysql(自写)
数据库·mysql
练习时长一年2 小时前
Spring代理的特点
java·前端·spring
G探险者2 小时前
常见线程池的创建方式及应用场景
后端
CYRUS_STUDIO2 小时前
FART 主动调用组件深度解析:破解 ART 下函数抽取壳的终极武器
android·java·逆向
bobz9653 小时前
virtio-networking 4: 介绍 vDPA 1
后端
叁沐3 小时前
MySQL 30 用动态的观点看加锁
mysql