数据库---Day10 索引

本系列可作为数据库学习系列的笔记,文中提到的一些练习的代码,小编会将代码复制下来,大家复制下来就可以练习了,方便大家学习。

点赞关注不迷路!您的点赞、关注和收藏是对小编最大的支持和鼓励!

系列文章目录

JAVA初阶---------已更完

JAVA数据结构---------已更完

数据库---Day 1 数据库基础

数据库---Day2 数据库操作

数据库---Day3 数据类型

数据库---Day4 数据表的操作

数据库---Day5 数据表的增删改查

数据库---Day6 数据库约束

数据库---Day7 数据表设计

数据库---Day8 多表联合查询

数据库---Day9 视图

数据库---Day10 索引


目录

目录

系列文章目录

目录

前言

一、为什么必须学习索引?

二、索引是什么?

[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 叉树仍然不够好)

八、B+树简介

[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 的过程中,索引是一个绕不开的核心知识点。很多初学者在刚接触数据库时,往往只关注 SELECTINSERTUPDATEDELETE 这些 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 = 1001id = 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

  1. 先和根节点 40 比较;

  2. 60 > 40,进入右子树;

  3. 再和 80 比较;

  4. 60 < 80,进入左子树;

  5. 找到 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 树的一种优化。它具有以下特点:

  1. 所有真实数据都存储在叶子节点;

  2. 非叶子节点只保存索引信息;

  3. 所有叶子节点之间通过链表连接;

  4. 叶子节点中的数据按照 key 有序排列;

  5. 树整体保持平衡,查询效率稳定。

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+树的真实数据只保存在叶子节点,非叶子节点只保存索引信息。

这样带来几个好处:

  1. 非叶子节点可以存储更多索引 key;

  2. 树高更低;

  3. 查询任意数据都要走到叶子节点,性能更稳定;

  4. 叶子节点天然适合范围查询;

  5. 顺序扫描效率更高。

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 页通常包含以下部分:

  1. 页文件头;

  2. 数据页头;

  3. 最小行和最大行;

  4. 数据行;

  5. 页目录;

  6. 页文件尾。

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 在数据页内部快速定位目标记录。

也就是说,完整的查询过程可以理解为两层定位:

  1. 通过 B+树定位到数据页;

  2. 通过页目录在页内定位到数据行。

十三、B+树在 MySQL 索引中的应用

13.1 B+树节点与页的关系

在 InnoDB 中,B+树中的每个节点,本质上都可以对应一个页。

  • 非叶子节点对应索引页;

  • 叶子节点对应数据页或索引页。

对于聚集索引来说,叶子节点保存完整数据行。

对于二级索引来说,叶子节点保存索引列的值和主键值。

13.2 查询过程示例

假设我们根据主键 id = 5 查询一条记录:

sql 复制代码
SELECT * FROM student WHERE id = 5;

B+树查询过程大致如下:

  1. 先读取根节点页;

  2. 在根节点中判断 id = 5 应该进入哪个子节点;

  3. 读取对应的中间索引页;

  4. 在中间索引页中继续判断;

  5. 最终定位到叶子节点;

  6. 在叶子节点中找到真实数据行。

如果 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 主键索引的特点

主键索引具有以下特点:

  1. 唯一;

  2. 非空;

  3. 一张表只能有一个主键;

  4. 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 什么是全文索引?

全文索引主要用于文本搜索,适合创建在 CHARVARCHARTEXT 等文本类型字段上。

例如文章表:

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 二级索引的叶子节点保存什么?

二级索引的叶子节点不会保存完整数据行,而是保存:

  1. 二级索引列的值;

  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_idid,没有 nameage。因此 MySQL 需要:

  1. 通过 idx_class_id 找到符合条件的主键 id;

  2. 根据 id 回到聚集索引中查询完整数据行;

  3. 取出 nameage

这就是回表。

二十一、索引覆盖

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_idname,而这两个字段都在索引 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_idorder_nocreate_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);

这个索引同时包含 snoclass_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;

如果只给 snoclass_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);

如果 nameclass_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;

通过这份脚本,可以练习:

  1. 创建数据库;

  2. 创建表;

  3. 插入测试数据;

  4. 创建普通索引;

  5. 创建唯一索引;

  6. 创建复合索引;

  7. 查看索引;

  8. 使用 EXPLAIN 观察索引使用情况;

  9. 删除索引。

三十一、常见面试题总结

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 示例讲解了如何创建、查看和删除索引,并总结了创建索引时的注意事项。

对于初学者来说,掌握索引至少要达到以下目标:

  1. 知道索引是什么;

  2. 知道为什么索引能提高查询效率;

  3. 理解 MySQL 为什么使用 B+树;

  4. 理解页是磁盘和内存交互的基本单位;

  5. 掌握主键索引、普通索引、唯一索引、复合索引的创建方式;

  6. 理解聚集索引、二级索引、回表和索引覆盖;

  7. 知道索引不是越多越好;

  8. 能够根据查询场景合理设计索引。

只要真正理解这些内容,就已经掌握了 MySQL 索引的核心基础。后续在学习 SQL 优化、执行计划、慢查询分析、联合索引最左前缀原则、索引失效场景时,也会更加容易理解。


总结

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

相关推荐
流星白龙1 小时前
【MySQL高阶】8.MySQL系统库
android·mysql·adb
Jul1en_1 小时前
【Redis】 集群概念
数据库·redis·哈希算法
我是一颗柠檬1 小时前
【Redis】有序集合与位图Day5(2026年)
数据库·redis·后端·缓存
我是一颗柠檬1 小时前
【Redis】持久化机制Day6(2026年)
数据库·redis·后端·缓存·database
huangdong_1 小时前
有什么软件可以下载淘宝和天猫店铺的商品图片?——从工具推荐到技术原理的完整解答
java·前端·数据库
我是一颗柠檬10 小时前
【MySQL全面教学】MySQL面试高频考点汇总Day15(2026年)
数据库·后端·mysql·面试
凯瑟琳.奥古斯特10 小时前
高阶子查询题目精炼
开发语言·数据库·python·职场和发展·数据库开发
身如柳絮随风扬10 小时前
数据库读写分离:从原理到实战,构建高并发系统
数据库·mysql