第一部分:索引基础
一、索引的本质
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,  -- 主键:内部id
    order_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:30
name:张三 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、分析、重构
到此这篇关于mysql数据库索引的文章就介绍到这了,更多相关mysql索引指南内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
 
             我要评论
我要评论 
                                             
                                             
                                            
发表评论