本系列可作为数据库学习系列的笔记,文中提到的一些练习的代码,小编会将代码复制下来,大家复制下来就可以练习了,方便大家学习。
点赞关注不迷路!您的点赞、关注和收藏是对小编最大的支持和鼓励!
系列文章目录
目录
目录
[2.1 索引的基本概念](#2.1 索引的基本概念)
[2.2 索引解决的核心问题](#2.2 索引解决的核心问题)
[3.1 提升数据检索效率](#3.1 提升数据检索效率)
[3.2 减少磁盘 I/O](#3.2 减少磁盘 I/O)
[3.3 加快排序和分组](#3.3 加快排序和分组)
[3.4 提高连接查询效率](#3.4 提高连接查询效率)
[五、Hash 索引](#五、Hash 索引)
[5.1 Hash 的特点](#5.1 Hash 的特点)
[5.2 Hash 索引的优点](#5.2 Hash 索引的优点)
[5.3 Hash 索引的问题](#5.3 Hash 索引的问题)
[5.4 为什么 MySQL 默认不使用 Hash 作为主要索引结构?](#5.4 为什么 MySQL 默认不使用 Hash 作为主要索引结构?)
[六、二叉搜索树为什么不适合做 MySQL 索引?](#六、二叉搜索树为什么不适合做 MySQL 索引?)
[6.1 二叉搜索树的基本思想](#6.1 二叉搜索树的基本思想)
[6.2 二叉搜索树的优点](#6.2 二叉搜索树的优点)
[6.3 二叉搜索树的问题](#6.3 二叉搜索树的问题)
[6.4 为什么 AVL 树和红黑树也不够好?](#6.4 为什么 AVL 树和红黑树也不够好?)
[七、N 叉树:降低树高的思路](#七、N 叉树:降低树高的思路)
[7.1 为什么要使用 N 叉树?](#7.1 为什么要使用 N 叉树?)
[7.2 N 叉树的优势](#7.2 N 叉树的优势)
[7.3 N 叉树仍然不够好](#7.3 N 叉树仍然不够好)
[8.1 什么是 B+树?](#8.1 什么是 B+树?)
[8.2 B+树的核心特点](#8.2 B+树的核心特点)
[九、B+树与 B 树的区别](#九、B+树与 B 树的区别)
[9.1 B 树的特点](#9.1 B 树的特点)
[9.2 B+树的特点](#9.2 B+树的特点)
[9.3 为什么 MySQL 更适合使用 B+树?](#9.3 为什么 MySQL 更适合使用 B+树?)
[十、MySQL 中的页结构](#十、MySQL 中的页结构)
[10.1 什么是页?](#10.1 什么是页?)
[10.2 为什么使用页?](#10.2 为什么使用页?)
[10.3 局部性原理](#10.3 局部性原理)
[11.1 页文件头和页文件尾](#11.1 页文件头和页文件尾)
[11.2 页主体](#11.2 页主体)
[11.3 数据行的组织方式](#11.3 数据行的组织方式)
[12.1 为什么需要页目录?](#12.1 为什么需要页目录?)
[12.2 页目录是什么?](#12.2 页目录是什么?)
[12.3 页目录如何加速查询?](#12.3 页目录如何加速查询?)
[12.4 页目录的意义](#12.4 页目录的意义)
[十三、B+树在 MySQL 索引中的应用](#十三、B+树在 MySQL 索引中的应用)
[13.1 B+树节点与页的关系](#13.1 B+树节点与页的关系)
[13.2 查询过程示例](#13.2 查询过程示例)
[13.3 三层 B+树能存多少数据?](#13.3 三层 B+树能存多少数据?)
[15.1 什么是主键索引?](#15.1 什么是主键索引?)
[15.2 主键索引的特点](#15.2 主键索引的特点)
[15.3 为什么推荐每张表都有主键?](#15.3 为什么推荐每张表都有主键?)
[16.1 什么是普通索引?](#16.1 什么是普通索引?)
[16.2 普通索引的特点](#16.2 普通索引的特点)
[16.3 普通索引适合哪些字段?](#16.3 普通索引适合哪些字段?)
[17.1 什么是唯一索引?](#17.1 什么是唯一索引?)
[17.2 唯一索引的特点](#17.2 唯一索引的特点)
[17.3 唯一索引适合哪些字段?](#17.3 唯一索引适合哪些字段?)
[18.1 什么是全文索引?](#18.1 什么是全文索引?)
[18.2 全文索引的使用场景](#18.2 全文索引的使用场景)
[19.1 什么是聚集索引?](#19.1 什么是聚集索引?)
[19.2 聚集索引的叶子节点保存什么?](#19.2 聚集索引的叶子节点保存什么?)
[19.3 没有主键时怎么办?](#19.3 没有主键时怎么办?)
[20.1 什么是非聚集索引?](#20.1 什么是非聚集索引?)
[20.2 二级索引的叶子节点保存什么?](#20.2 二级索引的叶子节点保存什么?)
[20.3 什么是回表查询?](#20.3 什么是回表查询?)
[21.1 什么是索引覆盖?](#21.1 什么是索引覆盖?)
[21.2 索引覆盖的优势](#21.2 索引覆盖的优势)
[21.3 覆盖索引示例](#21.3 覆盖索引示例)
[22.1 主键约束自动创建索引](#22.1 主键约束自动创建索引)
[22.2 唯一约束自动创建索引](#22.2 唯一约束自动创建索引)
[22.3 外键约束通常需要索引支持](#22.3 外键约束通常需要索引支持)
[23.1 创建表时直接指定主键](#23.1 创建表时直接指定主键)
[23.2 创建表时单独指定主键列](#23.2 创建表时单独指定主键列)
[23.3 修改已有表添加主键](#23.3 修改已有表添加主键)
[24.1 创建表时在字段后指定 unique](#24.1 创建表时在字段后指定 unique)
[24.2 创建表时单独指定唯一列](#24.2 创建表时单独指定唯一列)
[24.3 修改表添加唯一索引](#24.3 修改表添加唯一索引)
[25.1 创建表时指定索引列](#25.1 创建表时指定索引列)
[25.2 修改表添加普通索引](#25.2 修改表添加普通索引)
[25.3 单独创建索引并指定索引名](#25.3 单独创建索引并指定索引名)
[26.1 什么是复合索引?](#26.1 什么是复合索引?)
[26.2 创建表时指定复合索引](#26.2 创建表时指定复合索引)
[26.3 修改表添加复合索引](#26.3 修改表添加复合索引)
[26.4 单独创建复合索引并指定索引名](#26.4 单独创建复合索引并指定索引名)
[26.5 复合索引的使用场景](#26.5 复合索引的使用场景)
[27.1 使用 show keys 查看索引](#27.1 使用 show keys 查看索引)
[27.2 使用 show index 查看索引](#27.2 使用 show index 查看索引)
[27.3 使用 desc 查看简要信息](#27.3 使用 desc 查看简要信息)
[28.1 删除主键索引](#28.1 删除主键索引)
[28.2 删除普通索引、唯一索引、复合索引](#28.2 删除普通索引、唯一索引、复合索引)
[29.1 索引应该创建在高频查询字段上](#29.1 索引应该创建在高频查询字段上)
[29.2 索引会占用额外存储空间](#29.2 索引会占用额外存储空间)
[29.3 索引会影响写入性能](#29.3 索引会影响写入性能)
[29.4 不合理索引会降低性能](#29.4 不合理索引会降低性能)
[29.5 复合索引要注意字段顺序](#29.5 复合索引要注意字段顺序)
[31.1 什么是索引?](#31.1 什么是索引?)
[31.2 为什么 MySQL 使用 B+树作为索引结构?](#31.2 为什么 MySQL 使用 B+树作为索引结构?)
[31.3 Hash 索引为什么不适合作为默认索引?](#31.3 Hash 索引为什么不适合作为默认索引?)
[31.4 什么是聚集索引?](#31.4 什么是聚集索引?)
[31.5 什么是二级索引?](#31.5 什么是二级索引?)
[31.6 什么是回表?](#31.6 什么是回表?)
[31.7 什么是索引覆盖?](#31.7 什么是索引覆盖?)
[31.8 索引是不是越多越好?](#31.8 索引是不是越多越好?)
前言
小编作为新晋码农一枚,会定期整理一些写的比较好的代码,作为自己的学习笔记,会试着做一下批注和补充,如转载或者参考他人文献会标明出处,非商用,如有侵权会删改!欢迎大家斧正和讨论!
一、为什么必须学习索引?
在学习 MySQL 的过程中,索引是一个绕不开的核心知识点。很多初学者在刚接触数据库时,往往只关注 SELECT、INSERT、UPDATE、DELETE 这些 SQL 语句本身,认为只要会写查询语句,就能够完成数据库操作。但当数据量逐渐增大,例如一张表从几百条数据增长到几十万、几百万甚至上千万条数据时,同样一条 SQL 的执行速度可能会发生巨大变化。
例如,我们有一张学生表 student,里面保存了 1000 万条学生信息。如果没有索引,执行下面的 SQL:
sql
SELECT * FROM student WHERE id = 10086;
数据库可能需要从第一行开始,一行一行地扫描,直到找到 id = 10086 的记录。这个过程称为全表扫描。如果数据量很小,全表扫描的开销并不明显;但是当数据量非常大时,全表扫描会带来大量磁盘 I/O,查询速度会明显下降。
索引的作用,就是让数据库不必从头到尾逐行查找,而是通过一种高效的数据结构快速定位数据。它就像书籍的目录。我们查一本字典时,不会从第一页开始逐页翻找,而是会根据拼音、部首、笔画等索引快速找到目标汉字所在的位置。MySQL 索引的思想也是如此:通过提前建立好的数据结构,帮助数据库更快地找到目标数据。
因此,索引是数据库性能优化中最基础、最重要的一环。理解索引,不仅要知道如何创建和删除索引,更要理解索引背后的数据结构、B+树、InnoDB 页结构、聚集索引、非聚集索引、回表查询和索引覆盖等概念。只有理解这些底层原理,才能真正知道什么时候应该创建索引,什么时候索引可能失效,为什么索引过多反而会降低性能。
本文将围绕 MySQL 索引展开,系统讲解索引的基础概念、为什么使用索引、索引应该选择哪种数据结构、B+树在 MySQL 中的应用、MySQL 页结构、索引分类、索引创建与删除语法,以及创建索引时需要注意的问题。
二、索引是什么?
2.1 索引的基本概念
MySQL 的索引是一种数据结构,它可以帮助数据库更加高效地查询和更新表中的数据。索引会按照一定的规则组织表中的记录,使数据库在执行查询时,可以通过搜索索引来快速定位数据,而不是对整张表进行逐行扫描。
可以把索引理解为"数据的目录"。在现实生活中,目录的作用是帮助我们快速定位目标内容。例如一本字典通常会提供多种查字方式:
-
按拼音查找;
-
按部首查找;
-
按笔画查找。
如果没有这些目录,我们想找一个字,就只能从第一页开始逐页翻找,效率极低。有了目录之后,我们就可以先根据拼音、部首或笔画定位到大致范围,再快速找到目标字。
数据库中的索引也是类似的思想。假设有一张用户表:
sql
CREATE TABLE user (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50),
phone VARCHAR(20),
email VARCHAR(100)
);
如果我们经常根据手机号查询用户:
sql
SELECT * FROM user WHERE phone = '13800000000';
那么就可以给 phone 字段创建索引:
sql
CREATE INDEX idx_user_phone ON user(phone);
创建索引之后,MySQL 就可以通过索引结构快速定位手机号对应的用户记录,而不需要每次都扫描整张表。
2.2 索引解决的核心问题
索引主要解决的是查询效率问题。
在应用系统中,查询操作通常比插入、修改、删除操作更加频繁。例如电商系统中,用户浏览商品、搜索商品、查看订单、查询物流等操作,本质上都是查询。后台系统中,管理员查看用户列表、订单列表、统计数据,也大量依赖查询。
如果每次查询都要进行全表扫描,系统性能会非常差。索引通过提前组织数据,减少查询时需要扫描的数据量,从而提升查询速度。
但是索引并不是免费的。索引本身也需要占用磁盘空间,而且在执行插入、删除、更新操作时,MySQL 不仅要修改表中的数据,还要同步维护索引结构。因此,索引虽然可以提升查询性能,但也可能降低写入性能。
这就要求我们在实际开发中合理使用索引:该建索引的地方要建,不该建的地方不要乱建。
三、为什么要使用索引?
3.1 提升数据检索效率
使用索引最直接的目的就是提升数据检索效率。
假设有一张订单表 orders,包含 1000 万条订单数据:
sql
CREATE TABLE orders (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
order_no VARCHAR(50),
user_id BIGINT,
total_amount DECIMAL(10, 2),
create_time DATETIME
);
如果没有给 order_no 创建索引,那么执行下面的查询:
sql
SELECT * FROM orders WHERE order_no = 'ORD202605310001';
MySQL 可能需要逐行比较 order_no 的值,直到找到目标订单。数据量越大,扫描成本越高。
如果给 order_no 创建索引:
sql
CREATE INDEX idx_orders_order_no ON orders(order_no);
MySQL 就可以通过索引快速定位目标记录,大幅减少扫描范围。
3.2 减少磁盘 I/O
数据库性能瓶颈往往不是 CPU,而是磁盘 I/O。数据通常存储在磁盘中,查询时需要把磁盘中的数据读取到内存。如果每次查询都要读取大量磁盘页,性能自然会很差。
索引可以减少数据库需要读取的数据页数量。例如没有索引时,可能需要读取上万个数据页;有索引后,可能只需要读取几次索引页和一次数据页,就可以找到目标数据。
这也是为什么 MySQL 会采用 B+树作为索引结构。B+树的高度较低,通常三层左右就可以存储大量数据,因此一次查询只需要较少的磁盘 I/O。
3.3 加快排序和分组
索引不仅可以加快条件查询,还可以在某些情况下加快排序和分组操作。例如:
sql
SELECT * FROM orders ORDER BY create_time DESC;
如果 create_time 字段上有合适的索引,MySQL 可以利用索引本身的有序性,减少排序成本。
再如:
sql
SELECT user_id, COUNT(*)
FROM orders
GROUP BY user_id;
如果 user_id 上存在索引,也可能提升分组统计效率。
当然,是否真正使用索引,还要看 SQL 写法、索引设计和 MySQL 优化器的选择。
3.4 提高连接查询效率
在多表查询中,索引也非常重要。例如学生表和成绩表:
sql
SELECT s.name, sc.score
FROM student s
JOIN score sc ON s.id = sc.student_id
WHERE s.id = 1001;
如果 score.student_id 没有索引,那么连接时可能需要扫描大量成绩数据。给外键字段或连接字段建立索引,通常可以显著提高 JOIN 查询效率。
四、索引应该选择哪种数据结构?
索引的本质是一种数据结构。不同的数据结构会影响查询效率、范围查询能力、磁盘 I/O 次数和维护成本。MySQL 并不是随便选择 B+树作为索引结构的,而是在多种数据结构之间权衡之后的结果。
常见可以考虑的数据结构包括:
-
Hash;
-
二叉搜索树;
-
AVL 树和红黑树;
-
N 叉树;
-
B 树;
-
B+树。
下面逐一分析。
五、Hash 索引
5.1 Hash 的特点
Hash 表是一种非常常见的数据结构。它通过哈希函数把 key 映射到一个位置,从而实现快速查找。
理想情况下,Hash 查询的时间复杂度是 O(1)。也就是说,无论数据量有多少,只要哈希函数设计合理,都可以非常快地定位目标数据。
例如:
sql
SELECT * FROM user WHERE id = 1001;
如果使用 Hash 索引,MySQL 可以通过 id = 1001 计算出一个哈希值,然后直接找到对应位置的数据。
5.2 Hash 索引的优点
Hash 索引最大的优点是等值查询速度快。
例如下面这些查询非常适合 Hash:
sql
SELECT * FROM user WHERE id = 1;
SELECT * FROM user WHERE phone = '13800000000';
SELECT * FROM user WHERE username = 'tom';
这些查询的共同特点是:都是精确匹配。
5.3 Hash 索引的问题
虽然 Hash 查询很快,但它有一个非常明显的问题:不支持范围查询。
例如:
sql
SELECT * FROM student WHERE id > 1000 AND id < 2000;
对于这种范围查询,Hash 索引并不好用。因为 Hash 计算出来的值没有顺序关系,id = 1001 和 id = 1002 经过哈希函数计算后,可能分布在完全不同的位置。
此外,Hash 索引也不适合排序:
sql
SELECT * FROM student ORDER BY id;
因为 Hash 表中的数据不是按 key 有序排列的。
5.4 为什么 MySQL 默认不使用 Hash 作为主要索引结构?
MySQL 中最常用的 InnoDB 存储引擎默认采用 B+树索引,而不是 Hash 索引。主要原因是数据库查询不仅有等值查询,还有大量范围查询、排序查询和分组查询。
Hash 虽然等值查询快,但不支持范围查找,不适合作为通用索引结构。
六、二叉搜索树为什么不适合做 MySQL 索引?
6.1 二叉搜索树的基本思想
二叉搜索树是一种有序树结构。对于树中的任意节点:
-
左子树所有节点的值小于当前节点;
-
右子树所有节点的值大于当前节点。
例如插入数据 40、20、80、10、30、60、90,可以形成一棵二叉搜索树。
二叉搜索树的中序遍历结果是有序数组,因此它可以支持有序查找。
6.2 二叉搜索树的优点
在理想情况下,二叉搜索树的查询效率比较高。如果树是平衡的,查询时间复杂度大约是 O(logN)。
例如查找 60:
-
先和根节点
40比较; -
60 > 40,进入右子树; -
再和
80比较; -
60 < 80,进入左子树; -
找到
60。
6.3 二叉搜索树的问题
二叉搜索树有一个严重问题:在最坏情况下会退化成链表。
例如按顺序插入:
1, 2, 3, 4, 5, 6, 7
得到的树可能变成:
1
\
2
\
3
\
4
\
5
这种情况下,查询时间复杂度会从 O(logN) 退化为 O(N)。
6.4 为什么 AVL 树和红黑树也不够好?
AVL 树和红黑树是平衡或近似平衡的二叉树,它们可以避免普通二叉搜索树退化成链表的问题。
但是它们仍然是二叉结构。每个节点最多只有两个子节点。当数据量非常大时,树的高度仍然可能比较高。
在数据库系统中,每访问一个节点,都可能对应一次磁盘 I/O。磁盘 I/O 是非常昂贵的操作。如果树高太高,就意味着一次查询需要进行多次磁盘读取,性能不理想。
因此,虽然 AVL 树和红黑树在内存数据结构中很常见,但它们并不适合作为 MySQL 这种磁盘数据库的主要索引结构。
七、N 叉树:降低树高的思路
7.1 为什么要使用 N 叉树?
既然二叉树的问题在于树高较高,那么一个自然的优化思路就是:让每个节点保存更多子节点。
二叉树每个节点最多只有两个孩子,而 N 叉树每个节点可以有多个孩子。这样,在相同数据量下,N 叉树的高度会明显低于二叉树。
树高降低,就意味着查询时需要访问的节点数量减少,磁盘 I/O 次数也减少。
7.2 N 叉树的优势
假设有 100 万条数据:
-
如果使用二叉树,树高可能比较高;
-
如果使用 100 阶 N 叉树,每个节点最多有 100 个孩子,树高会大幅降低。
数据库系统最怕频繁磁盘 I/O,因此降低树高是索引设计的重要目标。
7.3 N 叉树仍然不够好
虽然 N 叉树降低了树高,但它还不是 MySQL 最终选择的索引结构。因为数据库索引不仅要支持单点查询,还要支持范围查询、排序遍历、稳定的数据维护等需求。
因此,MySQL 最终选择的是更适合磁盘存储和范围查询的 B+树。
八、B+树简介
8.1 什么是 B+树?
B+树是一种经常用于数据库和文件系统中的平衡查找树。MySQL InnoDB 存储引擎中的索引,底层主要采用 B+树结构。
B+树可以看成是对 B 树的一种优化。它具有以下特点:
-
所有真实数据都存储在叶子节点;
-
非叶子节点只保存索引信息;
-
所有叶子节点之间通过链表连接;
-
叶子节点中的数据按照 key 有序排列;
-
树整体保持平衡,查询效率稳定。
8.2 B+树的核心特点
第一,能够保持数据稳定有序
B+树中的数据按照索引 key 有序组织。无论是插入、删除还是查询,B+树都会通过节点分裂、合并等方式保持整体结构的平衡。
这使得 B+树的查询时间复杂度相对稳定,不容易出现严重退化。
第二,非叶子节点只保存索引
B+树的非叶子节点不保存完整数据,只保存索引 key 和子节点指针。这样一个索引页中可以容纳更多索引项,从而降低树高。
例如一个 16KB 的页,如果每条索引记录很小,就可以存储上千条索引项。树的分叉越多,高度越低,查询所需 I/O 次数越少。
第三,所有真实数据都保存在叶子节点
B+树的真实数据集中保存在叶子节点。这使得查询路径更加稳定。无论查询哪一条记录,都需要从根节点一路查找到叶子节点。
相比某些数据可能在非叶子节点中的结构,B+树的查询性能更加均衡。
第四,叶子节点之间形成有序链表
B+树的叶子节点之间通过指针连接成有序链表。这个设计对范围查询非常重要。
例如:
sql
SELECT * FROM student WHERE id BETWEEN 1000 AND 2000;
B+树可以先定位到 id = 1000 附近的叶子节点,然后沿着叶子节点链表向后扫描,直到超过 2000 为止。
如果没有叶子节点链表,范围查询就会复杂得多。
九、B+树与 B 树的区别
9.1 B 树的特点
B 树也是一种多路平衡查找树。它的每个节点中既可以保存索引 key,也可以保存真实数据。
也就是说,在 B 树中,数据可能存在于根节点、内部节点或叶子节点。
9.2 B+树的特点
B+树和 B 树最大的区别是:B+树的真实数据只保存在叶子节点,非叶子节点只保存索引信息。
这样带来几个好处:
-
非叶子节点可以存储更多索引 key;
-
树高更低;
-
查询任意数据都要走到叶子节点,性能更稳定;
-
叶子节点天然适合范围查询;
-
顺序扫描效率更高。
9.3 为什么 MySQL 更适合使用 B+树?
MySQL 数据库存储在磁盘中,查询时需要尽量减少磁盘 I/O。B+树的非叶子节点只保存索引信息,因此每个节点可以容纳更多 key,树高更低。
同时,MySQL 中大量查询都是范围查询,例如:
sql
SELECT * FROM orders WHERE create_time BETWEEN '2026-01-01' AND '2026-01-31';
SELECT * FROM product WHERE price >= 100 AND price <= 500;
SELECT * FROM student WHERE id > 1000;
B+树叶子节点之间的有序链表非常适合这类查询。
因此,B+树比 Hash、二叉树、红黑树和普通 B 树都更适合作为 MySQL InnoDB 的主要索引结构。
十、MySQL 中的页结构
10.1 什么是页?
在 InnoDB 中,页是内存与磁盘交互的最小单位。默认情况下,一个页的大小是 16KB。
也就是说,MySQL 从磁盘读取数据时,并不是只读取某一行,而是至少读取一个页。即使只查询一条记录,MySQL 也可能把这条记录所在的整个 16KB 页加载到内存中。
可以通过下面的 SQL 查看 InnoDB 页大小:
sql
SHOW VARIABLES LIKE 'innodb_page_size';
通常结果如下:
+------------------+-------+
| Variable_name | Value |
+------------------+-------+
| innodb_page_size | 16384 |
+------------------+-------+
16384 字节就是 16KB。
10.2 为什么使用页?
使用页是为了提高磁盘读取效率。
根据局部性原理,如果一个数据正在被访问,那么它附近的数据在未来也很可能被访问。因此,与其每次只读取一小段数据,不如一次读取一个页到内存中。
这样,如果后续访问的数据也在同一个页中,就可以直接从内存读取,不需要再次访问磁盘。
10.3 局部性原理
局部性原理包括两种:
第一,时间局部性
如果某个数据刚刚被访问,那么它在短时间内很可能再次被访问。
例如用户刚刚查看了某个订单,接下来可能还会再次刷新订单详情。
第二,空间局部性
如果某个数据被访问,那么它附近的数据也很可能被访问。
例如按主键连续查询多条记录时,这些记录可能存储在相邻位置,甚至位于同一个数据页中。
MySQL 采用页作为磁盘和内存之间的交互单位,正是为了利用局部性原理,减少磁盘 I/O。
十一、页的基本组成
一个 InnoDB 页通常包含以下部分:
-
页文件头;
-
数据页头;
-
最小行和最大行;
-
数据行;
-
页目录;
-
页文件尾。
11.1 页文件头和页文件尾
页文件头,也叫 File Header,用来记录页的基础信息,例如页号、上一页页号、下一页页号等。
页文件尾,也叫 File Trailer,通常用于校验页是否完整。
在索引页中,上一页页号和下一页页号非常重要。它们可以把多个页连接起来,形成双向链表。
这与 B+树叶子节点之间的链表结构密切相关。通过页之间的双向链表,MySQL 可以高效地进行范围扫描。
11.2 页主体
页主体是保存真实数据的主要区域。
当创建一个新的页时,InnoDB 会自动生成两个特殊行:
-
Infimum:页内最小行;
-
Supremum:页内最大行。
这两个行不保存真实业务数据,而是作为页内数据行链表的头和尾。
每条数据行中都有一个 next_record 指针,用来记录下一条数据行的位置。这样页内的多条数据行就可以组成一个单向链表。
11.3 数据行的组织方式
当向一个新页插入数据时,数据行会按照主键从小到大的顺序组织。
例如插入主键为 1、2、3、4、5 的数据后,页内结构可以理解为:
sql
Infimum -> 1 -> 2 -> 3 -> 4 -> 5 -> Supremum
如果继续插入主键为 6 的数据,就会追加到合适位置。
这种有序链表结构有利于页内查找和范围扫描。
十二、页目录:页内二分查找的关键
12.1 为什么需要页目录?
如果一个页中有几百条记录,最简单的查找方式是从 Infimum 开始,沿着链表一条一条查找。
但是这种方式效率较低。
假设一个页中有 500 条数据,如果每次都顺序遍历,最多可能比较 500 次。为了提高页内查询效率,InnoDB 引入了页目录。
12.2 页目录是什么?
页目录 Page Directory 是页中的一个结构。它会把页内的数据行进行分组,并把每个分组中最后一条记录的位置记录到页目录中。
页目录中的每一个位置称为一个槽。
简单理解:
sql
页目录 = 多个槽
槽 = 某个数据分组的最后一条记录的位置
12.3 页目录如何加速查询?
有了页目录后,MySQL 查找页内某条记录时,可以先在页目录中进行二分查找,找到目标数据可能所在的分组,然后再在分组内进行少量遍历。
因为每个分组的数据量有限,所以查询效率比从头到尾扫描整页要高很多。
例如要查找主键为 6 的记录,MySQL 可以先通过页目录判断它在哪个槽对应的分组中,然后只在该分组内部遍历几条记录即可。
12.4 页目录的意义
页目录体现了 InnoDB 对查询性能的精细优化。
B+树帮助 MySQL 快速定位到目标数据页,而页目录帮助 MySQL 在数据页内部快速定位目标记录。
也就是说,完整的查询过程可以理解为两层定位:
-
通过 B+树定位到数据页;
-
通过页目录在页内定位到数据行。
十三、B+树在 MySQL 索引中的应用
13.1 B+树节点与页的关系
在 InnoDB 中,B+树中的每个节点,本质上都可以对应一个页。
-
非叶子节点对应索引页;
-
叶子节点对应数据页或索引页。
对于聚集索引来说,叶子节点保存完整数据行。
对于二级索引来说,叶子节点保存索引列的值和主键值。
13.2 查询过程示例
假设我们根据主键 id = 5 查询一条记录:
sql
SELECT * FROM student WHERE id = 5;
B+树查询过程大致如下:
-
先读取根节点页;
-
在根节点中判断
id = 5应该进入哪个子节点; -
读取对应的中间索引页;
-
在中间索引页中继续判断;
-
最终定位到叶子节点;
-
在叶子节点中找到真实数据行。
如果 B+树有三层,那么一次查询通常需要读取:
根节点页 -> 中间索引页 -> 数据页
也就是大约三次 I/O。
13.3 三层 B+树能存多少数据?
假设一条用户数据大小为 1KB,一个 InnoDB 页大小为 16KB,那么一个数据页大约可以存储:
16KB / 1KB = 16 条数据
假设索引页中的一条索引记录包含:
-
BIGINT 主键:8 字节;
-
下一页地址:6 字节。
一条索引记录共约 14 字节。
一个 16KB 的索引页大约可以存储:
16 * 1024 / 14 ≈ 1170 条索引记录
如果 B+树高度为三层:
-
第一层:根节点;
-
第二层:中间索引节点;
-
第三层:叶子数据页。
那么可以存储的数据量约为:
1170 * 1170 * 16 = 21,902,400 条
也就是说,一个三层高度的 B+树,大约可以支撑两千多万条记录的查询,并且查询时只需要大约三次 I/O。
这也是 B+树作为数据库索引结构的重要优势。
十四、索引分类
MySQL 中的索引可以按照不同角度分类。常见索引包括:
-
主键索引;
-
普通索引;
-
唯一索引;
-
全文索引;
-
聚集索引;
-
非聚集索引;
-
复合索引;
-
覆盖索引。
下面分别讲解。
十五、主键索引
15.1 什么是主键索引?
当在一张表中定义主键时,MySQL 会自动为主键列创建主键索引。
例如:
sql
CREATE TABLE student (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50)
);
这里的 id 就是主键,MySQL 会自动为它创建主键索引。
15.2 主键索引的特点
主键索引具有以下特点:
-
唯一;
-
非空;
-
一张表只能有一个主键;
-
InnoDB 会使用主键作为聚集索引。
15.3 为什么推荐每张表都有主键?
在 InnoDB 中,数据是按照聚集索引组织的。如果表中有主键,InnoDB 会使用主键作为聚集索引。
如果没有主键,InnoDB 会寻找第一个唯一且非空的索引作为聚集索引。
如果既没有主键,也没有合适的唯一索引,InnoDB 会自动生成一个隐藏的 ROW_ID 作为聚集索引。
因此,推荐每张表都主动设计一个主键,通常使用自增 BIGINT 类型:
id BIGINT PRIMARY KEY AUTO_INCREMENT
这样结构清晰,也方便维护。
十六、普通索引
16.1 什么是普通索引?
普通索引是最基本的索引类型,没有唯一性限制。
例如:
sql
CREATE INDEX idx_student_name ON student(name);
这表示给 student 表的 name 字段创建一个普通索引。
16.2 普通索引的特点
普通索引可以加快查询,但不会限制字段值是否重复。
例如学生姓名可能重复,所以 name 字段适合创建普通索引,而不适合创建唯一索引。
16.3 普通索引适合哪些字段?
普通索引适合创建在高频查询字段上,例如:
-
用户手机号;
-
商品分类 ID;
-
订单创建时间;
-
学生班级 ID;
-
文章作者 ID。
示例:
sql
CREATE INDEX idx_orders_user_id ON orders(user_id);
CREATE INDEX idx_orders_create_time ON orders(create_time);
十七、唯一索引
17.1 什么是唯一索引?
唯一索引用于保证索引列的值不能重复。
例如用户表中的手机号通常不能重复:
sql
CREATE TABLE user (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
phone VARCHAR(20) UNIQUE,
username VARCHAR(50)
);
这里 phone 字段会自动创建唯一索引。
也可以手动添加:
sql
ALTER TABLE user ADD UNIQUE (phone);
17.2 唯一索引的特点
唯一索引和普通索引类似,都可以加快查询。
不同点在于,唯一索引要求索引列的值不能重复。
例如:
sql
INSERT INTO user(phone, username) VALUES('13800000000', 'Tom');
INSERT INTO user(phone, username) VALUES('13800000000', 'Jerry');
第二条插入会失败,因为手机号重复了。
17.3 唯一索引适合哪些字段?
唯一索引适合用于业务上必须唯一的字段,例如:
-
用户名;
-
手机号;
-
邮箱;
-
身份证号;
-
订单编号;
-
商品编码。
十八、全文索引
18.1 什么是全文索引?
全文索引主要用于文本搜索,适合创建在 CHAR、VARCHAR、TEXT 等文本类型字段上。
例如文章表:
sql
CREATE TABLE article (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(200),
content TEXT,
FULLTEXT KEY ft_content(content)
);
全文索引可以加快文章内容搜索。
18.2 全文索引的使用场景
全文索引适合用于:
-
文章搜索;
-
商品描述搜索;
-
评论内容搜索;
-
文档内容搜索。
不过在实际项目中,如果搜索功能比较复杂,通常会使用 Elasticsearch、OpenSearch 等专门的搜索引擎,而不是完全依赖 MySQL 全文索引。
十九、聚集索引
19.1 什么是聚集索引?
聚集索引是 InnoDB 中非常重要的概念。简单来说,聚集索引决定了表中数据的物理组织方式。
在 InnoDB 中,表数据本身就是按照聚集索引组织在 B+树中的。
通常情况下,主键索引就是聚集索引。
例如:
sql
CREATE TABLE student (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50),
age INT
);
这里 id 是主键,因此 InnoDB 会以 id 作为聚集索引。
19.2 聚集索引的叶子节点保存什么?
聚集索引的叶子节点保存完整的数据行。
例如 student 表中有:
id, name, age
那么聚集索引叶子节点中保存的是整行数据,而不仅仅是 id。
19.3 没有主键时怎么办?
如果表中没有定义主键,InnoDB 会选择第一个唯一且非空的列作为聚集索引。
如果没有这样的列,InnoDB 会自动生成一个隐藏的 6 字节 ROW_ID 作为聚集索引。
但是这种方式不利于开发者理解和维护,所以实际开发中应该主动给表设计主键。
二十、非聚集索引与二级索引
20.1 什么是非聚集索引?
除了聚集索引以外的索引,都可以称为非聚集索引,也叫二级索引。
例如:
sql
CREATE TABLE student (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50),
class_id BIGINT,
INDEX idx_class_id(class_id)
);
这里 id 是聚集索引,class_id 上的索引就是二级索引。
20.2 二级索引的叶子节点保存什么?
二级索引的叶子节点不会保存完整数据行,而是保存:
-
二级索引列的值;
-
对应行的主键值。
例如 idx_class_id(class_id) 的叶子节点可能保存:
class_id = 1, id = 1001
class_id = 1, id = 1002
class_id = 2, id = 1003
20.3 什么是回表查询?
当使用二级索引查询时,如果查询的字段不在二级索引中,MySQL 需要先通过二级索引找到主键值,再根据主键值去聚集索引中查找完整数据行。这个过程称为回表查询。
例如:
sql
SELECT name, age
FROM student
WHERE class_id = 1;
如果只有 class_id 索引,二级索引中只有 class_id 和 id,没有 name 和 age。因此 MySQL 需要:
-
通过
idx_class_id找到符合条件的主键 id; -
根据 id 回到聚集索引中查询完整数据行;
-
取出
name和age。
这就是回表。
二十一、索引覆盖
21.1 什么是索引覆盖?
如果一个查询使用了普通索引,并且查询需要的字段都能从该索引中直接获得,那么 MySQL 就不需要回表。这种现象称为索引覆盖。
例如有如下索引:
sql
CREATE INDEX idx_student_class_name ON student(class_id, name);
执行:
sql
SELECT class_id, name
FROM student
WHERE class_id = 1;
这个查询需要的字段是 class_id 和 name,而这两个字段都在索引 idx_student_class_name 中,因此可以直接从索引返回结果,不需要回表。
21.2 索引覆盖的优势
索引覆盖可以减少回表次数,从而降低磁盘 I/O,提高查询性能。
尤其在大数据量场景下,如果查询结果很多,回表成本可能非常高。通过合理设计复合索引,让查询尽可能走覆盖索引,是常见的 SQL 优化手段。
21.3 覆盖索引示例
假设有订单表:
sql
CREATE TABLE orders (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT,
order_no VARCHAR(50),
create_time DATETIME,
total_amount DECIMAL(10,2)
);
经常执行:
sql
SELECT order_no, create_time
FROM orders
WHERE user_id = 1001;
可以创建复合索引:
sql
CREATE INDEX idx_orders_user_order_time
ON orders(user_id, order_no, create_time);
这样查询需要的 user_id、order_no、create_time 都在索引中,可能形成覆盖索引,减少回表。
二十二、索引的自动创建
MySQL 在某些情况下会自动创建索引。
22.1 主键约束自动创建索引
当创建主键时,MySQL 会自动创建主键索引:
sql
CREATE TABLE t_test_pk (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(20)
);
22.2 唯一约束自动创建索引
当创建唯一约束时,MySQL 会自动创建唯一索引:
CREATE TABLE t_test_uk (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(20) UNIQUE
);
22.3 外键约束通常需要索引支持
外键字段通常也需要索引支持,这样可以提高关联查询和约束检查效率。
二十三、手动创建主键索引
23.1 创建表时直接指定主键
sql
CREATE TABLE t_test_pk (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(20)
);
这是最常见的方式。
23.2 创建表时单独指定主键列
sql
CREATE TABLE t_test_pk1 (
id BIGINT AUTO_INCREMENT,
name VARCHAR(20),
PRIMARY KEY (id)
);
这种写法适合后续定义复合主键,或者让表结构更清晰。
23.3 修改已有表添加主键
sql
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;
需要注意,如果要把某个字段改成自增列,该字段必须是 key,也就是必须有索引,通常是主键。
二十四、手动创建唯一索引
24.1 创建表时在字段后指定 unique
sql
CREATE TABLE t_test_uk (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(20) UNIQUE
);
24.2 创建表时单独指定唯一列
sql
CREATE TABLE t_test_uk1 (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(20),
UNIQUE (name)
);
24.3 修改表添加唯一索引
sql
CREATE TABLE t_test_uk2 (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(20)
);
ALTER TABLE t_test_uk2 ADD UNIQUE (name);
唯一索引可以防止重复数据插入,适合手机号、邮箱、订单号等业务唯一字段。
二十五、手动创建普通索引
25.1 创建表时指定索引列
sql
CREATE TABLE t_test_index (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(20) UNIQUE,
sno VARCHAR(10),
INDEX(sno)
);
这里给 sno 字段创建了普通索引。
25.2 修改表添加普通索引
sql
CREATE TABLE t_test_index1 (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(20),
sno VARCHAR(10)
);
ALTER TABLE t_test_index1 ADD INDEX (sno);
25.3 单独创建索引并指定索引名
sql
CREATE TABLE t_test_index2 (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(20),
sno VARCHAR(10)
);
CREATE INDEX index_name ON t_test_index2(sno);
实际开发中,建议给索引起一个有意义的名字,例如:
sql
CREATE INDEX idx_student_sno ON student(sno);
这样后续查看和删除索引时更清楚。
二十六、创建复合索引
26.1 什么是复合索引?
复合索引也叫组合索引,是指一个索引包含多个列。
例如:
sql
CREATE INDEX idx_sno_class_id ON student(sno, class_id);
这个索引同时包含 sno 和 class_id 两个字段。
26.2 创建表时指定复合索引
sql
CREATE TABLE t_test_index4 (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(20),
sno VARCHAR(10),
class_id BIGINT,
INDEX (sno, class_id)
);
26.3 修改表添加复合索引
sql
CREATE TABLE t_test_index5 (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(20),
sno VARCHAR(10),
class_id BIGINT
);
ALTER TABLE t_test_index5 ADD INDEX (sno, class_id);
26.4 单独创建复合索引并指定索引名
sql
CREATE TABLE t_test_index6 (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(20),
sno VARCHAR(10),
class_id BIGINT
);
CREATE INDEX index_name ON t_test_index6 (sno, class_id);
26.5 复合索引的使用场景
复合索引适合多条件查询:
sql
SELECT * FROM student
WHERE sno = 'S001' AND class_id = 10;
如果只给 sno 和 class_id 分别创建单列索引,不一定比一个合理的复合索引更高效。
复合索引还可以用于覆盖索引优化。例如:
sql
CREATE INDEX idx_student_class_name ON student(class_id, name);
对于下面的 SQL:
sql
SELECT class_id, name
FROM student
WHERE class_id = 1;
可能直接通过索引返回结果,避免回表。
二十七、查看索引
27.1 使用 show keys 查看索引
sql
SHOW KEYS FROM t_test_index6\G
执行后可以看到索引详细信息,包括:
-
表名;
-
索引是否唯一;
-
索引名;
-
索引中的列顺序;
-
列名;
-
索引类型;
-
是否可见。
例如输出中:
sql
Key_name: PRIMARY
Column_name: id
Index_type: BTREE
说明 id 字段上有一个主键索引,索引类型是 BTREE。
如果看到:
sql
Key_name: index_name
Seq_in_index: 1
Column_name: sno
说明 sno 是复合索引中的第一列。
如果看到:
sql
Seq_in_index: 2
Column_name: class_id
说明 class_id 是该复合索引中的第二列。
27.2 使用 show index 查看索引
sql
SHOW INDEX FROM t_test_index6;
这个命令和 SHOW KEYS 类似,也可以查看表中已有索引。
27.3 使用 desc 查看简要信息
sql
DESC t_test_index6;
DESC 可以查看表结构,也可以通过 Key 字段简单判断某列是否有索引。
例如:
-
PRI表示主键; -
UNI表示唯一索引; -
MUL表示普通索引或复合索引中的列。
二十八、删除索引
28.1 删除主键索引
删除主键索引的语法是:
sql
ALTER TABLE 表名 DROP PRIMARY KEY;
示例:
sql
ALTER TABLE t_test_index6 DROP PRIMARY KEY;
但是如果主键字段是自增列,可能会报错:
sql
Incorrect table definition; there can be only one auto column and it must be defined as a key
原因是 MySQL 要求自增列必须是一个 key。如果直接删除主键,会导致自增列不再是 key,因此报错。
解决方式是先去掉自增属性:
sql
ALTER TABLE t_test_index6 MODIFY id BIGINT;
然后再删除主键:
sql
ALTER TABLE t_test_index6 DROP PRIMARY KEY;
28.2 删除普通索引、唯一索引、复合索引
删除其他索引的语法是:
sql
ALTER TABLE 表名 DROP INDEX 索引名;
例如删除 index_name:
sql
ALTER TABLE t_test_index6 DROP INDEX index_name;
删除后可以查看索引:
sql
SHOW KEYS FROM t_test_index6\G
如果返回空结果,说明索引已经删除。
二十九、创建索引的注意事项
29.1 索引应该创建在高频查询字段上
索引不是越多越好,而是应该创建在经常作为查询条件的字段上。
适合创建索引的字段:
sql
WHERE user_id = ?
WHERE phone = ?
WHERE order_no = ?
WHERE create_time BETWEEN ? AND ?
JOIN ... ON a.id = b.user_id
不适合创建索引的字段:
-
很少查询的字段;
-
数据重复度极高的字段;
-
经常更新但很少查询的字段;
-
小表中没有明显查询压力的字段。
29.2 索引会占用额外存储空间
每创建一个索引,MySQL 都需要额外维护一棵索引树。索引本身会占用磁盘空间。
如果一张表有很多字段,每个字段都创建索引,磁盘占用会明显增加。
29.3 索引会影响写入性能
执行插入、更新、删除操作时,MySQL 不仅要修改数据行,还要维护相关索引。
例如执行:
sql
INSERT INTO student(name, class_id) VALUES('Tom', 1);
如果 name、class_id 上都有索引,那么插入数据时,MySQL 还需要把对应索引树也更新。
因此,索引越多,写入成本越高。
29.4 不合理索引会降低性能
有些索引看起来有用,但实际效果很差。例如性别字段:
sql
gender TINYINT
如果只有男、女两种值,那么重复度非常高。给这种字段创建索引,可能并不能明显减少扫描数据量,甚至优化器可能不会使用该索引。
29.5 复合索引要注意字段顺序
复合索引中的字段顺序非常重要。
例如:
sql
CREATE INDEX idx_student_sno_class ON student(sno, class_id);
这个索引更适合:
sql
WHERE sno = 'S001';
WHERE sno = 'S001' AND class_id = 1;
但不一定适合:
sql
WHERE class_id = 1;
因为复合索引通常遵循最左前缀原则。索引从最左边字段开始匹配,如果没有使用最左列,索引可能无法充分发挥作用。
三十、完整练习脚本
下面提供一份完整练习脚本,适合初学者跟着练习。
sql
DROP DATABASE IF EXISTS index_demo;
CREATE DATABASE index_demo DEFAULT CHARSET utf8mb4;
USE index_demo;
CREATE TABLE student (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
sno VARCHAR(20),
name VARCHAR(50),
class_id BIGINT,
phone VARCHAR(20),
email VARCHAR(100),
age INT
);
INSERT INTO student(sno, name, class_id, phone, email, age) VALUES
('S001', 'Tom', 1, '13800000001', 'tom@example.com', 18),
('S002', 'Jerry', 1, '13800000002', 'jerry@example.com', 19),
('S003', 'Alice', 2, '13800000003', 'alice@example.com', 18),
('S004', 'Bob', 2, '13800000004', 'bob@example.com', 20),
('S005', 'Lucy', 3, '13800000005', 'lucy@example.com', 21);
CREATE INDEX idx_student_sno ON student(sno);
ALTER TABLE student ADD UNIQUE (phone);
CREATE INDEX idx_student_class_name ON student(class_id, name);
SHOW KEYS FROM student\G
EXPLAIN SELECT * FROM student WHERE sno = 'S001';
EXPLAIN SELECT class_id, name FROM student WHERE class_id = 1;
ALTER TABLE student DROP INDEX idx_student_sno;
SHOW INDEX FROM student;
通过这份脚本,可以练习:
-
创建数据库;
-
创建表;
-
插入测试数据;
-
创建普通索引;
-
创建唯一索引;
-
创建复合索引;
-
查看索引;
-
使用
EXPLAIN观察索引使用情况; -
删除索引。
三十一、常见面试题总结
31.1 什么是索引?
索引是 MySQL 中用于提高查询效率的数据结构。它类似书籍目录,可以帮助数据库快速定位目标数据,避免全表扫描。
31.2 为什么 MySQL 使用 B+树作为索引结构?
因为 B+树具有树高低、查询稳定、支持范围查询、叶子节点有序链表、磁盘 I/O 次数少等优点,非常适合数据库场景。
31.3 Hash 索引为什么不适合作为默认索引?
Hash 索引等值查询很快,但不支持范围查询,也不适合排序和分组,因此不适合作为通用索引结构。
31.4 什么是聚集索引?
聚集索引决定数据的物理组织方式。在 InnoDB 中,主键索引通常就是聚集索引,聚集索引的叶子节点保存完整数据行。
31.5 什么是二级索引?
除聚集索引之外的索引称为二级索引。二级索引的叶子节点保存索引列和主键值。
31.6 什么是回表?
使用二级索引查询时,如果查询字段不在二级索引中,MySQL 需要先通过二级索引找到主键,再根据主键去聚集索引中查询完整数据行,这个过程称为回表。
31.7 什么是索引覆盖?
如果查询需要的字段都能从索引中直接获得,不需要回表,这种情况称为索引覆盖。
31.8 索引是不是越多越好?
不是。索引会占用额外存储空间,并且会增加插入、更新、删除操作的维护成本。索引应该创建在高频查询字段上,而不是盲目创建。
三十二、总结
索引是 MySQL 性能优化中最基础、最重要的知识点。理解索引不能只停留在"创建一个 index 可以加快查询"这个层面,而应该进一步理解索引背后的数据结构和存储原理。
本文从索引的基本概念开始,讲解了为什么需要索引,分析了 Hash、二叉搜索树、N 叉树、B+树等数据结构的优缺点,并重点说明了为什么 MySQL InnoDB 选择 B+树作为主要索引结构。
随后,本文介绍了 InnoDB 页结构,包括页文件头、页文件尾、页主体、Infimum、Supremum、数据行链表和页目录。理解页结构之后,就能更清楚地认识到:MySQL 查询数据并不是单纯在逻辑表中查找,而是在磁盘页、B+树和页目录之间完成多层定位。
在索引分类部分,本文讲解了主键索引、普通索引、唯一索引、全文索引、聚集索引、非聚集索引、二级索引、回表查询和索引覆盖等内容。最后通过 SQL 示例讲解了如何创建、查看和删除索引,并总结了创建索引时的注意事项。
对于初学者来说,掌握索引至少要达到以下目标:
-
知道索引是什么;
-
知道为什么索引能提高查询效率;
-
理解 MySQL 为什么使用 B+树;
-
理解页是磁盘和内存交互的基本单位;
-
掌握主键索引、普通索引、唯一索引、复合索引的创建方式;
-
理解聚集索引、二级索引、回表和索引覆盖;
-
知道索引不是越多越好;
-
能够根据查询场景合理设计索引。
只要真正理解这些内容,就已经掌握了 MySQL 索引的核心基础。后续在学习 SQL 优化、执行计划、慢查询分析、联合索引最左前缀原则、索引失效场景时,也会更加容易理解。

总结
以上就是今天要讲的内容,本文简单记录了数据库学习内容,仅作为一份简单的笔记使用,大家根据注释理解,您的点赞关注收藏就是对小编最大的鼓励!