一、索引基础:定义与优缺点
1. 索引的定义
索引是存储引擎用于快速找到记录的一种数据结构,类比书籍的目录:无需逐页翻阅,通过目录可直接定位目标章节。MySQL 中索引是在存储引擎层实现的(InnoDB/MyISAM 实现逻辑不同),而非服务器层。
2. 索引的优点
| 核心优势 | 具体说明 |
|---|---|
| 提升查询效率 | 避免全表扫描(Full Table Scan),尤其对大表,查询耗时从秒级降至毫秒级 |
| 加速排序 / 分组 | 索引本身是有序的,可直接利用索引完成ORDER BY/GROUP BY,无需额外排序 |
| 优化连接查询 | 多表JOIN时,索引可快速匹配关联字段(如JOIN t1 ON t1.id = t2.t1_id) |
| 减少数据扫描量 | 存储引擎通过索引直接定位数据页,而非遍历所有数据页 |
3. 索引的缺点
| 核心缺点 | 具体说明 |
|---|---|
| 增加写操作开销 | 插入 / 更新 / 删除数据时,需同步维护索引结构(如 B+ 树的分裂 / 合并),写性能下降 |
| 占用额外存储空间 | 索引是独立的数据结构,会消耗磁盘 / 内存空间(如一张 10GB 的表,索引可能占 2-3GB) |
| 索引失效风险 | 不规范的 SQL 会导致索引失效(如LIKE '%xxx'),反而增加查询开销 |
| 维护成本高 | 过多索引会增加优化器的选择成本(MySQL 需评估哪个索引最优),且索引碎片会随写操作累积 |
二、索引底层数据结构选型:从 Hash 到 B+ 树
索引的性能核心取决于底层数据结构,MySQL 主流选择 B+ 树,而非 Hash / 二叉树 / AVL / 红黑树,以下是关键选型对比:
1. Hash 表
- 结构原理:基于键值对(key-value),通过哈希函数将索引键映射到数组下标,冲突时用链表 / 红黑树解决。
- 适用场景:等值查询(
=/IN),如内存数据库(Memcached)。 - 缺点:
- 不支持范围查询(
>/</BETWEEN):哈希值无序,无法通过区间定位; - 不支持排序:哈希结果无顺序,无法满足
ORDER BY; - 不支持最左前缀匹配:哈希函数对完整键计算,无法利用部分前缀;
- 不支持范围查询(
- MySQL 中的应用:Memory 存储引擎支持 Hash 索引,InnoDB 的自适应哈希索引(AHI)是内存级优化(非显式索引)。
哈希算法
一、哈希算法的核心定义
哈希算法(又称散列算法)是一种将任意长度的输入(键 / 数据)映射为固定长度输出的函数,输出结果称为「哈希值(Hash Value)」或「散列值」。
- 输入:可以是字符串、数字、文件等任意数据(哈希算法中称为「键(Key)」);
- 输出:固定长度的二进制 / 十进制值(如 MD5 输出 128 位,MySQL 中 Hash 索引的哈希值通常为整型);
- 核心目标:通过哈希值快速定位数据,将「无序的键」转化为「可直接寻址的索引」。
直观示例
# 简单哈希函数:取字符串ASCII码之和模10 def simple_hash(key): ascii_sum = sum(ord(c) for c in key) return ascii_sum % 10 # 输入输出示例 simple_hash("zhangsan") # 计算:z(122)+h(104)+a(97)+n(110)+g(103)+s(115)+a(97)+n(110) = 858 → 858%10=8 simple_hash("lisi") # l(108)+i(105)+s(115)+i(105) = 433 → 433%10=3二、哈希算法的核心特性
一个「好的哈希算法」需满足以下关键特性,也是其能支撑 Hash 索引的基础:
特性 定义 作用 确定性 相同输入必然得到相同输出 保证每次查询同一键时,哈希值一致,可准确定位数据 高效性 计算哈希值的时间复杂度接近 O (1) 避免哈希计算本身成为性能瓶颈 均匀性(低冲突) 不同输入的哈希值均匀分布在输出空间 减少「哈希冲突」,避免某一哈希值对应大量数据 不可逆性 无法通过哈希值反推原始输入 (非索引必需,但加密类哈希算法必备,如 MD5/SHA) 抗碰撞性 难以找到两个不同输入得到相同哈希值 (索引场景下允许少量碰撞,可通过冲突解决机制处理) 三、哈希冲突与解决机制
哈希算法的「均匀性」是理想状态,实际中不同输入可能得到相同哈希值(即「哈希冲突」),这是 Hash 表设计的核心问题,常见解决方式有 2 种:
1. 链地址法(拉链法,MySQL Hash 索引默认)
- 原理:将哈希表的每个位置(桶)对应一个链表,冲突的键值对依次挂载到链表中;
- 查询流程:
- 计算键的哈希值,定位到哈希表的对应桶;
- 遍历桶对应的链表,对比原始键(避免哈希值相同但原始键不同),找到目标值;
- 优缺点:可以实现简单,扩容成本低;但是冲突严重时链表过长,查询效率从 O (1) 降至 O (n)(优化:链表转红黑树,如 Java HashMap)。
2. 开放地址法(线性探测 / 二次探测)
- 原理:哈希冲突时,按固定规则(如线性探测:当前位置 + 1)寻找下一个空桶;
- 示例:哈希值为 8 的位置已被占用,则尝试 9、10… 直到找到空桶;
- 优缺点:无需额外链表,空间利用率高;但是易产生「堆积」(连续多个桶被占用),且删除数据时需标记「已删除」(避免断链)。
四、MySQL 中哈希算法的应用场景
MySQL 中哈希算法主要用于两类场景,核心是「等值查询优化」:
1. 显式 Hash 索引(Memory 存储引擎)
- 支持引擎:仅 Memory 引擎(Heap)支持显式 Hash 索引(默认索引类型),InnoDB/MyISAM 不支持;
- 哈希函数:MySQL 内置哈希函数(如
HASH()),将索引键映射为整型哈希值;- 查询特性:等值查询(
=/IN)极快:O (1) 时间复杂度,远超 B + 树的 O (logn);但是不支持范围查询(>/</BETWEEN)、排序(ORDER BY)、最左前缀匹配;- 示例:
-- 创建Memory表并指定Hash索引 CREATE TABLE user ( id INT, name VARCHAR(20), INDEX idx_name USING HASH (name) -- 显式指定Hash索引 ) ENGINE = MEMORY;2. 自适应哈希索引(AHI,InnoDB 内置优化)
- 定义:InnoDB 针对高频访问的索引页,自动在内存中构建 Hash 索引(无需用户配置);
- 触发条件:某一索引键被频繁查询(InnoDB 判断为「热数据」),自动将该键的「索引值→数据页指针」映射为 Hash 表;
- 核心特性:仅存在于内存,不占用磁盘空间;仅优化等值查询(如主键 / 唯一索引的高频等值查询);自动维护:数据更新时同步更新 Hash 表,无需人工干预;
- 配置参数:
SHOW VARIABLES LIKE 'innodb_adaptive_hash_index'; -- 查看是否开启(默认ON) SET GLOBAL innodb_adaptive_hash_index = OFF; -- 关闭(仅建议特殊场景)五、哈希算法 vs B + 树算法(MySQL 索引选型对比)
哈希算法和 B + 树算法是 MySQL 索引的两大核心算法,适用场景差异显著,具体对比如下:
维度 哈希算法(Hash 索引) B + 树算法(B + 树索引) 查询类型 仅支持等值查询(= /IN)支持等值 / 范围 / 排序 / 分组查询 时间复杂度 等值查询 O (1)(无冲突),冲突时 O (n) 稳定 O (logn)(不受数据分布影响) 数据有序性 哈希值无序,无法利用顺序 索引键有序,支持范围 / 排序 存储介质 适合内存(Memory 引擎) 适合磁盘(InnoDB/MyISAM) 写性能 插入 / 删除仅需维护 Hash 表,无平衡开销 插入 / 删除需维护 B + 树平衡(分裂 / 合并) 适用场景 高频等值查询的内存表(如临时缓存表) 磁盘表的通用查询(绝大多数业务场景) 六、常见哈希算法
除 MySQL 索引内置的哈希算法外,常见的通用哈希算法包括:
算法 输出长度 特点 应用场景 MD5 128 位 速度快,已被破解(不适合加密) 文件校验、数据去重 SHA-1 160 位 安全性高于 MD5,已被破解 早期数字签名 SHA-256 256 位 安全性高,计算稍慢 区块链、加密存储 CRC32 32 位 极快,冲突率较高 数据校验(如网络传输) MurmurHash 32/64 位 均匀分布、速度快 分布式缓存(如 Redis)、Hash 索引 七、总结
哈希算法的核心价值是「将任意输入映射为固定长度的哈希值,实现 O (1) 级等值查询」,但受限于「无序性」,仅适合内存表的高频等值查询场景;而 MySQL 绝大多数业务场景(磁盘存储、需范围 / 排序查询)选择 B + 树算法,本质是平衡「磁盘 IO 效率」和「查询通用性」。
关键结论如下:
- 哈希算法是 Hash 索引的核心,优势是等值查询快,劣势是不支持范围 / 排序;
- InnoDB 的自适应哈希索引(AHI)是内存级优化,仅提升高频等值查询性能;
- 业务开发中,除非是 Memory 引擎的临时缓存表,否则优先选择 B + 树索引(主键 / 二级索引),而非依赖 Hash 索引。
2. 二叉查找树(BST)
- 结构原理:每个节点左子树 < 节点 < 右子树,单链表式遍历。
- 致命问题:极端情况下退化为单链表(如插入有序数据),查询时间复杂度从 O (logn) 降至 O (n),完全失去索引意义。
3. AVL 树(平衡二叉树)
- 结构原理:在 BST 基础上强制保证「左右子树高度差 ≤1」,通过旋转(左旋 / 右旋)维持平衡。
- 缺点:
- 旋转操作频繁:插入 / 更新时需多次旋转,写性能极低;
- 节点存储密度低:每个节点仅存 1 条数据,树高度过高(百万数据需约 20 层),磁盘 IO 次数多(磁盘 IO 是性能瓶颈)。
4. 红黑树
- 结构原理:弱平衡二叉树(放弃「高度差 ≤1」,改为颜色约束),旋转次数远少于 AVL 树。
- 缺点:
- 仍为二叉树,树高度高(百万数据约 20 层),磁盘 IO 次数多;
- 节点仅存 1 条数据,不适合磁盘存储的大规模数据。
5. B 树 & B+ 树(MySQL 核心选型)
(1)B 树(多路平衡查找树)
- 结构原理:
- 多叉树(m 阶 B 树每个节点最多 m 个子节点),节点存储「键 + 数据」;
- 所有键分布在整棵树中,叶节点无指针相连。
- 缺点:
- 节点存储数据,导致单节点可存储的键数量少,树高度仍较高;
- 范围查询需多次回溯父节点,效率低。
(2)B+ 树(B 树的优化版,InnoDB 默认)
- 核心结构(以 InnoDB 为例):
- 非叶节点:仅存「键 + 子节点指针」,不存数据,单节点可存储更多键,树高度极低(百万数据仅需 3-4 层);
- 叶节点:存储「键 + 数据(或主键指针)」,且所有叶节点通过双向链表相连,有序排列;
- 所有查询最终落到叶节点,保证查询稳定性(无论等值 / 范围查询,IO 次数固定)。
- 核心优势(为何成为 MySQL 首选):
- 树高度低,磁盘 IO 次数少(3 层 B+ 树仅需 3 次 IO 即可定位数据);
- 叶节点链表有序,范围查询只需遍历链表,无需回溯;
- 非叶节点仅存键,缓存命中率高(内存可缓存更多索引节点);
- 写操作的平衡成本低(旋转 / 分裂次数远少于二叉树)。
| 数据结构 | 适用场景 | 时间复杂度(查询) | 磁盘 IO 友好性 | 写性能 |
|---|---|---|---|---|
| Hash 表 | 等值查询(内存) | O (1)(无冲突) | 差 | 中 |
| BST | 无(仅理论) | O (n)(最坏) | 差 | 中 |
| AVL 树 | 无 | O(logn) | 差 | 差 |
| 红黑树 | 内存级小数据 | O(logn) | 差 | 中 |
| B 树 | 部分数据库(如 MongoDB) | O(logn) | 中 | 中 |
| B+ 树 | MySQL 大规模磁盘数据 | O(logn) | 优 | 优 |
三、索引类型:从功能到存储结构
索引按维度分类小结:
| 分类维度 | 具体索引类型 | 核心特征 | 代表引擎支持 |
|---|---|---|---|
| 存储结构(物理) | 聚簇索引、非聚簇索引 | 索引与数据是否物理绑定 | 聚簇:InnoDB;非聚簇:MyISAM/InnoDB 二级索引 |
| 功能用途(逻辑) | 主键索引、二级索引(辅助索引)、唯一索引、普通索引、全文索引、空间索引 | 索引的业务作用和约束 | 主键 / 唯一 / 普通:所有引擎;全文:InnoDB (5.6+)/MyISAM;空间:MyISAM/InnoDB (5.7+) |
| 数据结构(底层) | B + 树索引、Hash 索引、R 树索引 | 索引的底层实现结构 | B + 树:所有引擎默认;Hash:Memory;R 树:空间索引 |
| 字段数量 | 单列索引、联合索引(复合索引) | 索引包含的字段个数 | 所有引擎支持 |
| 访问方式 | 聚簇索引(直接访问数据)、覆盖索引(无需回表)、前缀索引(字符串截取) | 索引的访问 / 使用方式 | 覆盖 / 前缀:InnoDB/MyISAM;聚簇:InnoDB |
| 索引状态 | 显式索引、隐式索引 | 是否由用户手动创建 | 显式:用户创建;隐式:InnoDB 自动生成(如 row_id) |
1. 按功能分类:主键索引 vs 二级索引
(1)主键索引(Primary Key)
- 定义:唯一标识记录的索引,InnoDB 中必须有且仅有一个(无显式指定时,InnoDB 会隐式生成 6 字节的 row_id 作为主键)。
- 特性:
- 唯一性:主键值不可重复,且非空(
NOT NULL); - 高性能:InnoDB 中主键索引是聚簇索引的核心,查询效率最高。
- 唯一性:主键值不可重复,且非空(
(2)二级索引(辅助索引 / 非主键索引)
- 定义:除主键索引外的所有索引(如
UNIQUE/INDEX/FULLTEXT)。 - 特性:
- 叶节点存储「索引键 + 主键值」(而非直接存储数据);
- 查询时需先通过二级索引找到主键,再通过主键索引查数据(即「回表查询」)。
2. 按存储结构分类:聚簇索引 vs 非聚簇索引
(1)聚簇索引(聚集索引,Clustered Index)
- 核心定义:索引与数据物理存储在一起,索引的叶节点就是数据页(InnoDB 中主键索引就是聚簇索引)。
- InnoDB 聚簇索引规则:
- 优先使用显式主键(
PRIMARY KEY)作为聚簇索引; - 无显式主键时,选择第一个非空唯一索引(
UNIQUE NOT NULL); - 无上述索引时,隐式生成 6 字节的
row_id作为聚簇索引。
- 优先使用显式主键(
- 优点:
- 查询效率极高:叶节点直接存数据,无需回表;
- 数据有序存储:主键相邻的记录物理存储也相邻,范围查询效率高。
- 缺点:
- 主键更新成本高:需移动数据物理位置,且会导致所有二级索引失效;
- 页分裂风险:插入数据时若数据页已满,会触发页分裂,产生碎片;
- 不适合用 UUID 作为主键:UUID 无序,插入时会频繁触发页分裂,且主键占空间大(二级索引叶节点存主键,会放大索引体积)。
(2)非聚簇索引(非聚集索引,Non-Clustered Index)
- 核心定义:索引与数据物理分离,索引叶节点仅存「索引键 + 数据指针 / 主键值」(MyISAM 所有索引都是非聚簇索引,InnoDB 二级索引也是)。
- MyISAM 非聚簇索引特性:
- 索引文件与数据文件分离(
.MYI存索引,.MYD存数据); - 叶节点存储「索引键 + 数据行的物理地址」。
- 索引文件与数据文件分离(
- 优点:
- 主键更新成本低:仅需修改索引,无需移动数据;
- 无页分裂问题:数据存储与索引无关。
- 缺点:
- 查询需回表:所有索引查询都需先找指针,再查数据,效率低于聚簇索引;
- 数据无序:索引有序但数据物理存储无序,范围查询需多次 IO。
(3)关键问题:非聚簇索引一定回表吗?
答案:不一定!覆盖索引可避免回表。
- 回表查询:通过二级索引找到主键后,需再查主键索引获取完整数据(2 次 B+ 树查询);
- 覆盖索引(Covering Index):查询的所有字段都包含在二级索引中,无需回表(仅需 1 次 B+ 树查询)。
- 示例:表
user有索引idx_name_age (name, age),执行SELECT name, age FROM user WHERE name = '张三',索引已覆盖查询字段,无需回表。
- 示例:表
3. 覆盖索引与联合索引
(1)覆盖索引
- 定义:索引包含查询所需的所有字段,可直接从索引中返回结果,无需回表。
- 适用场景:高频查询的小字段组合(如
SELECT id, name FROM user WHERE phone = '138xxxx',建立idx_phone_id_name (phone, id, name)即可覆盖)。 - 优势:减少 IO 次数,提升查询效率(避免二次查询主键索引)。
(2)联合索引(复合索引)
- 定义:基于多个字段的索引(如
idx_a_b_c (a, b, c)),本质是按字段顺序拼接后排序的 B+ 树。 - 核心原则:最左前缀匹配
- 联合索引的生效规则是「从左到右匹配,遇到范围查询(
>/</BETWEEN/LIKE '%xxx')停止」; - 示例:索引
idx_a_b_c (a, b, c):- 生效:
WHERE a=1/WHERE a=1 AND b=2/WHERE a=1 AND b=2 AND c=3; - 失效:
WHERE b=2(跳过左前缀 a)/WHERE a=1 AND c=3(跳过 b)/WHERE a>1 AND b=2(a 是范围查询,b 失效)。
- 生效:
- 联合索引的生效规则是「从左到右匹配,遇到范围查询(
- 设计技巧:将高频等值查询字段放左侧,范围查询字段放右侧。
4. 索引下推(ICP,Index Condition Pushdown)
- 定义:MySQL 5.6+ 特性,将部分
WHERE条件下推到存储引擎层过滤,减少回表次数。 - 原理:
- 无 ICP:存储引擎仅按索引找主键,服务器层再过滤条件(即使条件字段在索引中);
- 有 ICP:存储引擎在遍历索引时,直接过滤索引中包含的条件字段,仅返回符合条件的主键。
- 示例:索引
idx_name_age (name, age),查询SELECT * FROM user WHERE name LIKE '张%' AND age = 20:- 无 ICP:存储引擎返回所有
name LIKE '张%'的主键,服务器层再过滤age=20; - 有 ICP:存储引擎遍历索引时,直接过滤
age≠20的记录,仅返回符合条件的主键,减少回表数量。
- 无 ICP:存储引擎返回所有
四、正确使用索引的建议
1. 选择合适的字段创建索引
- 优先给「高频查询字段」(如
WHERE/JOIN/ORDER BY字段)建索引; - 优先给「区分度高的字段」(如手机号、身份证,而非性别、状态)建索引(区分度 = 唯一值数 / 总记录数,越高索引效率越高);
- 避免给「大量重复值的字段」(如
status只有 0/1)建单列索引(可作为联合索引的右侧字段)。
2. 被频繁更新的字段慎重建索引
- 索引会放大写操作开销(插入 / 更新 / 删除需维护索引);
- 示例:订单状态(
order_status)若每秒更新上千次,建索引会导致写性能暴跌,可改为「定时批量查询 + 内存缓存」。
3. 限制每张表的索引数量
- 建议单表索引数 ≤5 个(过多索引会导致:写性能下降、优化器选择索引耗时增加、索引碎片累积);
- 核心原则:「按需创建,够用即可」,避免为小众查询场景建索引。
4. 优先建立联合索引,而非单列索引
- 单列索引存在「索引合并」成本(MySQL 需合并多个单列索引结果),效率低于联合索引;
- 示例:查询
WHERE a=1 AND b=2,建idx_a_b (a, b)远优于分别建idx_a (a)和idx_b (b)。
5. 避免冗余索引
- 冗余索引:索引 A 完全包含索引 B 的功能,如已建
idx_a_b (a, b),再建idx_a (a)就是冗余; - 危害:增加写开销、浪费存储空间、优化器判断成本高;
- 检测工具:
sys.schema_redundant_indexes(MySQL 8.0+)或pt-duplicate-key-checker。
6. 字符串字段用前缀索引代替普通索引
- 问题:长字符串(如文本、URL)建普通索引会导致索引体积过大;
- 解决方案:截取前缀建索引(如
INDEX idx_title (title(10))); - 注意:前缀长度需平衡「区分度」和「索引体积」(通过
SELECT COUNT(DISTINCT LEFT(title, 10)) / COUNT(*) FROM table评估区分度)。
7. 避免索引失效
| 失效场景 | 示例 | 解决方案 |
|---|---|---|
| 函数 / 运算操作索引字段 | WHERE SUBSTR(name, 1, 2) = '张三' | 改为前缀索引 +WHERE name LIKE '张三%' |
| 隐式类型转换 | WHERE phone = 138xxxx(phone 是 varchar) | 改为WHERE phone = '138xxxx' |
| 模糊查询以 % 开头 | WHERE name LIKE '%张三' | 改为全文索引(FULLTEXT)或业务优化 |
| OR 连接无索引字段 | WHERE a=1 OR b=2(仅 a 有索引) | 改为UNION或给 b 建索引 |
| 联合索引跳过左前缀 | WHERE b=2(索引 idx_a_b (a,b)) | 调整查询条件或索引顺序 |
8. 删除长期未使用的索引
- 检测方法:
sys.schema_unused_indexes(MySQL 8.0+)或开启slow_query_log分析; - 注意:删除前需备份,且在低峰期操作(避免锁表)。
9. 分析 SQL 语句是否使用索引
- 核心工具:
EXPLAIN(执行计划),关键字段解读:type:访问类型(ALL= 全表扫描,ref= 非唯一索引,range= 范围查询,const= 主键 / 唯一索引等值查询,越靠右越好);key:实际使用的索引(NULL表示未用索引);rows:预估扫描行数(越少越好);Extra:额外信息(Using index= 覆盖索引,Using where= 服务器层过滤,Using filesort= 需额外排序,需优化)。
五、总结
MySQL 索引的核心是「B+ 树」,其设计围绕「减少磁盘 IO」展开;聚簇索引(InnoDB 主键)是性能核心,二级索引需注意回表问题;索引优化的本质是「平衡查询与写性能」,避免过度索引和索引失效。
关键原则如下:
- 索引是「双刃剑」,需按需创建,而非越多越好;
- 联合索引遵循「最左前缀」,字符串优先用前缀索引;
- 用
EXPLAIN验证索引是否生效,定期清理冗余 / 未使用索引; - InnoDB 主键优先选自增整型(避免 UUID),减少页分裂和索引体积。