MySQL索引指南
文章目录
- 第一部分:索引基础
- 一、索引的本质
- 1.1 什么是索引?
- 1.2 索引解决的核心问题
- 1.3 索引的核心思想
- 二、索引的底层数据结构
- 2.1 为什么选择B+树?
- 常见数据结构对比
- 为什么不用其他结构?
- 2.2 B+树详解
- B+树的特点
- B+树容量计算
- 第二部分:索引类型
- 三、MySQL索引分类
- 3.1 按数据结构分类
- 3.2 按物理存储分类
- 3.3 按逻辑功能分类
- 3.4 按字段数量分类
- 3.5 按功能分类
- 四、主键索引与唯一索引对比
- 4.1 核心区别
- 4.2 NULL值处理差异
- 4.3 使用场景对比
- 五、聚簇索引与非聚簇索引
- 5.1 聚簇索引(InnoDB主键)
- 5.2 非聚簇索引(二级索引)
- 5.3 InnoDB vs MyISAM
- 第三部分:索引失效
- 六、索引失效的13种场景
- 6.1 索引列使用函数或表达式
- 6.2 隐式类型转换
- 6.3 LIKE前缀模糊
- 6.4 OR条件有非索引列
- 6.5 负向查询
- 6.6 违反最左前缀原则
- 6.7 范围查询后的列失效
- 6.8-6.13 其他失效场景
- 七、如何诊断索引问题
- 7.1 使用EXPLAIN
- 7.2 查看索引统计
- 第四部分:索引优化
- 八、索引设计原则
- 8.1 什么时候建索引
- 8.2 索引设计6大原则
- 8.3 联合索引设计
- 九、索引优化实战
- 9.1 案例1:函数导致索引失效
- 9.2 案例2:覆盖索引优化
- 9.3 案例3:深度分页优化
- 第五部分:面试必备
- 十、高频面试问题
- Q1:什么是索引?作用是什么?
- Q2:为什么使用B+树?
- Q3:聚簇索引和非聚簇索引的区别?
- Q4:主键索引和唯一索引的区别?
- Q5:什么是覆盖索引?
- Q6:联合索引的最左前缀原则?
- Q7:什么情况下索引会失效?
- Q8:如何设计高效索引?
- Q9:索引越多越好吗?
- Q10:主键为什么推荐自增整型?
- 总结
- 索引核心知识图谱
- 索引使用口诀
第一部分:索引基础
一、索引的本质
1.1 什么是索引?
索引(Index) 是一种数据结构,用于帮助数据库高效地查找数据。
类比理解:
图书馆找书的方式:没有索引 = 逐本翻找(全表扫描)
有索引 = 通过目录快速定位(索引查找)书籍目录:
- 章节目录:按顺序组织(B+树)
- 关键词索引:按字母排序(索引)
- 页码:快速定位到具体位置(主键)
1.2 索引解决的核心问题
问题场景:
-- 100万条用户数据
SELECT * FROM users WHERE name = '张三';
无索引:
- 需要扫描100万行数据(全表扫描)
- 时间复杂度:O(n)
- IO次数:取决于表的大小
有索引:
- 通过B+树快速定位
- 时间复杂度:O(log n)
- IO次数:通常3-4次(树的高度)
性能对比:
数据量 无索引耗时 有索引耗时 提升倍数
1万 10ms 1ms 10倍
10万 100ms 1ms 100倍
100万 1000ms 1ms 1000倍
1000万 10000ms 2ms 5000倍
1.3 索引的核心思想
- 空间换时间:额外存储索引数据,换取查询速度
- 有序结构:数据按特定规则排列,支持快速查找
- 减少IO:减少磁盘访问次数(数据库性能瓶颈)
二、索引的底层数据结构
2.1 为什么选择B+树?
常见数据结构对比
数据结构 | 查找时间 | 优点 | 缺点 | 适用场景 |
---|---|---|---|---|
数组 | O(n) | 简单 | 查询慢 | 小数据量 |
有序数组 | O(log n) | 查询快 | 插入/删除慢 | 静态数据 |
链表 | O(n) | 插入快 | 查询慢 | 不适合索引 |
二叉搜索树 | O(log n) ~ O(n) | 平衡 | 可能退化 | 内存结构 |
AVL树 | O(log n) | 严格平衡 | 旋转成本高 | 内存结构 |
红黑树 | O(log n) | 平衡 | 树高较高 | 内存结构 |
哈希表 | O(1) | 查询极快 | 不支持范围查询 | 等值查询 |
B树 | O(log n) | 多路查找 | 非叶子节点存数据 | 文件系统 |
B+树 | O(log n) | 范围查询快 | 结构复杂 | 数据库索引 ✅ |
为什么不用其他结构?
1. 哈希表
优点:O(1) 查找
缺点:❌ 不支持范围查询(WHERE age > 20)❌ 不支持排序(ORDER BY)❌ 不支持模糊查询(LIKE 'zhang%')❌ 哈希冲突问题使用场景:Memory引擎的HASH索引
2. 二叉搜索树/红黑树
缺点:❌ 树高太高:100万数据,红黑树高度约20层 = 20次IO❌ 每个节点只存一个值,IO利用率低❌ 不适合磁盘存储B+树高度:100万数据,高度约3-4层 = 3-4次IO
3. B树
缺点:❌ 非叶子节点存储数据,导致每个节点存储的索引项更少❌ 范围查询需要中序遍历,效率低B+树优势:✅ 非叶子节点只存索引,每个节点可存更多索引项✅ 叶子节点用链表连接,范围查询效率高
2.2 B+树详解
B+树的特点
B+树结构示例(3阶B+树):[20, 50] ← 根节点(只存索引)/ | \[10,15] [30,40] [60,70] ← 中间节点(只存索引)/ | \ / | \ / | \[...] [...] [...] [...] [...] ← 叶子节点(存数据+指针)↔ ↔ ↔ ↔ ↔ ← 双向链表
核心特性:
-
所有数据都在叶子节点
- 非叶子节点只存索引键
- 叶子节点存完整数据(或指针)
-
叶子节点形成有序链表
- 支持高效的范围查询
- 支持顺序遍历
-
高度平衡
- 所有叶子节点在同一层
- 查询任意数据的IO次数一致
-
多路查找
- 每个节点可以有多个子节点(不是二叉)
- 减少树的高度,减少IO次数
B+树容量计算
假设:
- InnoDB页大小:16KB
- 索引键(bigint):8字节
- 指针大小:6字节
- 数据行大小:1KB
非叶子节点:
每个节点能存储的索引项数 = 16KB / (8B + 6B) ≈ 1170个3层B+树能存储的数据量:第1层(根):1个节点第2层:1170个节点第3层(叶子):1170 × 1170 = 1,368,900个节点每个叶子节点存储数据:16KB / 1KB = 16条
总数据量:1,368,900 × 16 ≈ 2000万条结论:3次IO可以查询2000万数据!
第二部分:索引类型
三、MySQL索引分类
3.1 按数据结构分类
- B+树索引(默认)
- Hash索引(Memory引擎)
- 全文索引(FULLTEXT)
- 空间索引(SPATIAL)
3.2 按物理存储分类
- 聚簇索引(Clustered Index)
- 非聚簇索引(Secondary Index)
3.3 按逻辑功能分类
- 主键索引(PRIMARY KEY)
- 唯一索引(UNIQUE)
- 普通索引(INDEX)
- 全文索引(FULLTEXT)
3.4 按字段数量分类
- 单列索引
- 联合索引(复合索引)
3.5 按功能分类
- 覆盖索引(Covering Index)
- 前缀索引(Prefix Index)
- 索引下推(ICP)
四、主键索引与唯一索引对比
4.1 核心区别
特性 | 主键索引 | 唯一索引 |
---|---|---|
NULL值 | ❌ 不允许 | ✅ 允许(可多个NULL) |
数量 | ❌ 只能1个 | ✅ 可以多个 |
索引类型 | 聚簇索引(InnoDB) | 二级索引 |
性能 | ⭐⭐⭐⭐⭐ 最快(无需回表) | ⭐⭐⭐⭐ 较快(需回表) |
作用 | 唯一标识每一行 | 保证业务唯一性 |
关系:
主键 = 唯一索引 + NOT NULL + 聚簇索引(InnoDB) + 只能一个
4.2 NULL值处理差异
-- 主键:不允许NULL
CREATE TABLE t1 (id INT PRIMARY KEY, name VARCHAR(50));
INSERT INTO t1 VALUES (NULL, '张三'); -- ❌ 报错-- 唯一索引:允许多个NULL
CREATE TABLE t2 (id INT PRIMARY KEY AUTO_INCREMENT,email VARCHAR(100) UNIQUE
);
INSERT INTO t2 (email) VALUES (NULL); -- ✅ 成功
INSERT INTO t2 (email) VALUES (NULL); -- ✅ 成功(SQL标准:NULL != NULL)
INSERT INTO t2 (email) VALUES ('a@example.com'); -- ✅ 成功
INSERT INTO t2 (email) VALUES ('a@example.com'); -- ❌ 报错(重复)
4.3 使用场景对比
-- 主键索引:系统标识
CREATE TABLE users (id BIGINT PRIMARY KEY AUTO_INCREMENT, -- 主键:系统唯一标识username VARCHAR(50) UNIQUE, -- 唯一索引:业务约束email VARCHAR(100) UNIQUE, -- 唯一索引:业务约束phone VARCHAR(20) UNIQUE -- 唯一索引:业务约束
);-- 订单表:主键 + 业务唯一号
CREATE TABLE orders (id BIGINT PRIMARY KEY AUTO_INCREMENT, -- 主键:内部IDorder_no VARCHAR(32) UNIQUE, -- 唯一索引:业务订单号idempotent_key VARCHAR(64) UNIQUE -- 唯一索引:幂等性控制
);
五、聚簇索引与非聚簇索引
5.1 聚簇索引(InnoDB主键)
定义:数据行和索引存储在一起,叶子节点存储完整数据行。
特点:
- ✅ 一个表只能有一个
- ✅ 主键就是聚簇索引
- ✅ 查询速度快(无需回表)
- ❌ 插入可能导致页分裂
结构:
聚簇索引(主键索引):[20, 50]/ | \[10,15] [30,40] [60,70]/ | | |[完整数据行] [完整数据行] [完整数据行]id:10 id:20 id:30name:张三 name:李四 name:王五age:25 age:30 age:35... ... ...
5.2 非聚簇索引(二级索引)
定义:索引和数据分离,叶子节点存储主键值。
结构:
二级索引(name索引):[李四, 王五]/ | \[张三] [李四,刘备] [王五,赵六]| | |[name:张三 [name:李四 [name:王五→ id:10] → id:20] → id:30]↓回表查询主键索引
回表查询:
SELECT * FROM users WHERE name = '张三';执行过程:
1. 在name索引中找到 '张三' → 得到主键id=10
2. 回到主键索引,通过id=10找到完整数据行
3. 返回结果共2次索引查找(2次B+树遍历)
5.3 InnoDB vs MyISAM
特性 | InnoDB | MyISAM |
---|---|---|
主键索引 | 聚簇索引(数据在索引中) | 非聚簇索引(数据在独立文件) |
二级索引 | 存储主键值 | 存储数据地址 |
回表性能 | 需要通过主键索引查询 | 直接通过地址查询 |
数据文件 | .ibd(索引+数据) | .MYD(数据)+ .MYI(索引) |
事务支持 | ✅ 支持 | ❌ 不支持 |
行锁 | ✅ 支持 | ❌ 不支持(表锁) |
第三部分:索引失效
六、索引失效的13种场景
6.1 索引列使用函数或表达式
原因:破坏索引的有序性。
-- ❌ 失效
SELECT * FROM users WHERE YEAR(create_time) = 2024;
SELECT * FROM users WHERE age + 1 = 25;-- ✅ 正确
SELECT * FROM users
WHERE create_time >= '2024-01-01' AND create_time < '2025-01-01';
SELECT * FROM users WHERE age = 24;
6.2 隐式类型转换
-- 假设phone是VARCHAR
-- ❌ 失效
SELECT * FROM users WHERE phone = 13800138000;-- ✅ 正确
SELECT * FROM users WHERE phone = '13800138000';规则:
- 字符串索引 + 数值查询 = 失效
- 数值索引 + 字符串查询 = 生效
6.3 LIKE前缀模糊
-- ❌ 失效
SELECT * FROM users WHERE name LIKE '%张三';
SELECT * FROM users WHERE name LIKE '%张三%';-- ✅ 生效
SELECT * FROM users WHERE name LIKE '张三%';
6.4 OR条件有非索引列
-- ❌ 失效
SELECT * FROM users WHERE name = '张三' OR age = 20; -- age无索引-- ✅ 解决方案
-- 方案1:为age创建索引
-- 方案2:改写为UNION
SELECT * FROM users WHERE name = '张三'
UNION
SELECT * FROM users WHERE age = 20;
6.5 负向查询
-- ❌ 通常失效
SELECT * FROM users WHERE status != 1;
SELECT * FROM users WHERE id NOT IN (1, 2, 3);-- ✅ 改写为正向
SELECT * FROM users WHERE status IN (0, 2, 3, 4);
6.6 违反最左前缀原则
-- 假设索引:INDEX(name, age, city)-- ✅ 走索引
WHERE name = '张三'
WHERE name = '张三' AND age = 20
WHERE name = '张三' AND age = 20 AND city = '北京'-- ❌ 不走索引
WHERE age = 20
WHERE city = '北京'
WHERE age = 20 AND city = '北京'
6.7 范围查询后的列失效
-- 假设索引:INDEX(name, age, city)
SELECT * FROM users
WHERE name = '张三' AND age > 20 AND city = '北京';
-- name和age走索引,city不走(age是范围查询)
6.8-6.13 其他失效场景
详见完整表格(篇幅原因省略,包括IS NULL、区分度低、结果集过大、IN过多、字符集不一致等)
七、如何诊断索引问题
7.1 使用EXPLAIN
EXPLAIN SELECT * FROM users WHERE name = '张三';关键字段:
- type: ALL(最差)< index < range < ref < const(最优)
- key: 实际使用的索引(NULL表示未使用)
- rows: 扫描行数(越少越好)
- Extra: - Using index(覆盖索引,最优)- Using filesort(需要排序优化)- Using temporary(需要临时表优化)
7.2 查看索引统计
-- 查看索引信息
SHOW INDEX FROM users;-- 更新统计信息
ANALYZE TABLE users;-- 查看未使用的索引
SELECT * FROM sys.schema_unused_indexes;-- 查看冗余索引
SELECT * FROM sys.schema_redundant_indexes;
第四部分:索引优化
八、索引设计原则
8.1 什么时候建索引
✅ 应该建索引:
- WHERE条件列
- ORDER BY排序列
- GROUP BY分组列
- JOIN连接列
- 高频查询列
- 区分度高的列(>0.1)
❌ 不应该建索引:
- 区分度低的列(如性别)
- 频繁更新的列
- 大字段(TEXT、BLOB)
- 很少使用的列
- 小表(<1000行)
8.2 索引设计6大原则
1. 选择性原则:高区分度列优先
2. 最左前缀原则:联合索引按顺序使用
3. 覆盖索引原则:包含查询所需列
4. 索引顺序原则:等值>范围、高频>低频
5. 避免冗余原则:删除重复索引
6. 主键设计原则:自增整型优先
8.3 联合索引设计
-- 查询需求:WHERE status = ? AND user_id = ? ORDER BY create_time-- ✅ 推荐
CREATE INDEX idx_status_user_create
ON orders(status, user_id, create_time);-- 理由:
-- 1. status和user_id是等值查询,放前面
-- 2. create_time是排序列,放后面(避免filesort)
-- 3. 可以覆盖索引(如果只查这些列)
九、索引优化实战
9.1 案例1:函数导致索引失效
-- ❌ 问题SQL(1.5秒)
SELECT * FROM orders WHERE DATE(create_time) = '2024-01-01';-- EXPLAIN: type=ALL, rows=1000000-- ✅ 优化后(0.01秒)
SELECT * FROM orders
WHERE create_time >= '2024-01-01' AND create_time < '2024-01-02';-- EXPLAIN: type=range, rows=1000, 提升150倍
9.2 案例2:覆盖索引优化
-- ❌ 问题SQL(10ms,需要回表)
SELECT user_id, order_no, amount, status
FROM orders
WHERE user_id = 12345;-- ✅ 建立覆盖索引
CREATE INDEX idx_user_order_amount_status
ON orders(user_id, order_no, amount, status);-- 优化后(2ms,无需回表),提升5倍
9.3 案例3:深度分页优化
-- ❌ 慢(5秒)
SELECT * FROM orders ORDER BY id LIMIT 1000000, 10;-- ✅ 快(0.5秒)- 子查询
SELECT o.*
FROM orders o
INNER JOIN (SELECT id FROM orders ORDER BY id LIMIT 1000000, 10
) t ON o.id = t.id;-- ✅ 最快(0.01秒)- 游标分页
SELECT * FROM orders
WHERE id > 1000000 -- 上次最后一条ID
ORDER BY id LIMIT 10;
第五部分:面试必备
十、高频面试问题
Q1:什么是索引?作用是什么?
索引是一种数据结构(通常是B+树),用于帮助数据库高效查找数据。
作用:
- 大幅提升查询速度(O(n) → O(log n))
- 加速排序和分组
- 避免全表扫描
- 保证数据唯一性
Q2:为什么使用B+树?
B+树 vs 红黑树:
- 红黑树:20层 = 20次IO
- B+树:3-4层 = 3-4次IO
B+树 vs B树:
- B+树叶子节点有序链表,范围查询更快
Q3:聚簇索引和非聚簇索引的区别?
特性 | 聚簇索引 | 非聚簇索引 |
---|---|---|
数据存储 | 索引和数据一起 | 索引和数据分离 |
叶子节点 | 存储完整数据行 | 存储主键值 |
数量限制 | 一个表只能一个 | 可以多个 |
查询性能 | 快(无需回表) | 慢(需要回表) |
Q4:主键索引和唯一索引的区别?
- NULL值:主键不允许,唯一索引允许
- 数量:主键1个,唯一索引多个
- 类型:主键是聚簇索引,唯一索引是二级索引
- 性能:主键查询更快(无需回表)
Q5:什么是覆盖索引?
查询的所有列都在索引中,无需回表查询。
优势:减少IO、提升速度、减少锁竞争
-- 假设索引:INDEX(name, age)-- ✅ 覆盖索引
SELECT name, age FROM users WHERE name = '张三';
-- Extra: Using index-- ❌ 非覆盖索引
SELECT * FROM users WHERE name = '张三';
-- 需要回表
Q6:联合索引的最左前缀原则?
联合索引INDEX(a, b, c)
相当于创建了(a)
、(a,b)
、(a,b,c)
三个索引。
查询必须包含最左列才能使用索引。
Q7:什么情况下索引会失效?
- 索引列使用函数
- 隐式类型转换
- LIKE前缀模糊
- OR条件有非索引列
- 负向查询
- 违反最左前缀
- 查询结果集过大
Q8:如何设计高效索引?
设计原则:
- 选择性原则:高区分度列(>0.1)
- 最左前缀原则:合理安排顺序
- 覆盖索引原则:包含查询列
- 索引顺序:等值>范围、高频>低频
Q9:索引越多越好吗?
不是!索引有代价:
- ❌ 占用存储空间
- ❌ 降低写入性能
- ❌ 增加维护成本
建议:
- 单表索引不超过5个
- 定期删除未使用的索引
Q10:主键为什么推荐自增整型?
特性 | 自增整型 | UUID |
---|---|---|
存储 | 4/8字节 | 36字节 |
插入 | 顺序插入 | 随机插入,页分裂 |
性能 | 整型比较快 | 字符串比较慢 |
分布式 | 需特殊处理 | 天然支持 |
折中方案:雪花算法(BIGINT,有序)
总结
索引核心知识图谱
索引本质:空间换时间的数据结构↓
底层结构:B+树(多路平衡查找树)↓
索引分类:
├─ 主键索引(聚簇索引,最快)
├─ 唯一索引(二级索引,较快)
├─ 普通索引(二级索引,常用)
└─ 特殊索引(全文、空间)↓
优化要点:
├─ 避免失效(13种场景)
├─ 合理设计(6大原则)
├─ 覆盖索引(减少回表)
└─ 定期维护(ANALYZE、OPTIMIZE)↓
性能提升:O(n) → O(log n)
索引使用口诀
索引设计三原则:选择性、有序性、覆盖性
索引失效三大忌:函数、类型、通配符
索引优化三步走:EXPLAIN、分析、重构