MySQL索引指南

文章目录


第一部分:索引基础

一、索引的本质

1.1 什么是索引?

索引(Index) 是一种数据结构,用于帮助数据库高效地查找数据。

类比理解

复制代码
图书馆找书的方式:

没有索引 = 逐本翻找(全表扫描)
有索引   = 通过目录快速定位(索引查找)

书籍目录:
- 章节目录:按顺序组织(B+树)
- 关键词索引:按字母排序(索引)
- 页码:快速定位到具体位置(主键)

1.2 索引解决的核心问题

问题场景

sql 复制代码
-- 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 索引的核心思想

  1. 空间换时间:额外存储索引数据,换取查询速度
  2. 有序结构:数据按特定规则排列,支持快速查找
  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]     ← 中间节点(只存索引)
         /  |  \   /  |  \   /  |  \
       [...] [...] [...] [...] [...] ← 叶子节点(存数据+指针)
         ↔     ↔     ↔     ↔     ↔   ← 双向链表

核心特性

  1. 所有数据都在叶子节点

    • 非叶子节点只存索引键
    • 叶子节点存完整数据(或指针)
  2. 叶子节点形成有序链表

    • 支持高效的范围查询
    • 支持顺序遍历
  3. 高度平衡

    • 所有叶子节点在同一层
    • 查询任意数据的IO次数一致
  4. 多路查找

    • 每个节点可以有多个子节点(不是二叉)
    • 减少树的高度,减少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值处理差异

sql 复制代码
-- 主键:不允许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 使用场景对比

sql 复制代码
-- 主键索引:系统标识
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]
                    ↓
            回表查询主键索引

回表查询

sql 复制代码
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 索引列使用函数或表达式

原因:破坏索引的有序性。

sql 复制代码
-- ❌ 失效
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 隐式类型转换

sql 复制代码
-- 假设phone是VARCHAR
-- ❌ 失效
SELECT * FROM users WHERE phone = 13800138000;

-- ✅ 正确
SELECT * FROM users WHERE phone = '13800138000';

规则:
- 字符串索引 + 数值查询 = 失效
- 数值索引 + 字符串查询 = 生效

6.3 LIKE前缀模糊

sql 复制代码
-- ❌ 失效
SELECT * FROM users WHERE name LIKE '%张三';
SELECT * FROM users WHERE name LIKE '%张三%';

-- ✅ 生效
SELECT * FROM users WHERE name LIKE '张三%';

6.4 OR条件有非索引列

sql 复制代码
-- ❌ 失效
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 负向查询

sql 复制代码
-- ❌ 通常失效
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 违反最左前缀原则

sql 复制代码
-- 假设索引: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 范围查询后的列失效

sql 复制代码
-- 假设索引: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

sql 复制代码
EXPLAIN SELECT * FROM users WHERE name = '张三';

关键字段:
- type: ALL(最差)< index < range < ref < const(最优)
- key: 实际使用的索引(NULL表示未使用)
- rows: 扫描行数(越少越好)
- Extra: 
  - Using index(覆盖索引,最优)
  - Using filesort(需要排序优化)
  - Using temporary(需要临时表优化)

7.2 查看索引统计

sql 复制代码
-- 查看索引信息
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 联合索引设计

sql 复制代码
-- 查询需求: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 复制代码
-- ❌ 问题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 复制代码
-- ❌ 问题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:深度分页优化

sql 复制代码
-- ❌ 慢(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+树),用于帮助数据库高效查找数据。

作用

  1. 大幅提升查询速度(O(n) → O(log n))
  2. 加速排序和分组
  3. 避免全表扫描
  4. 保证数据唯一性

Q2:为什么使用B+树?

B+树 vs 红黑树

  • 红黑树:20层 = 20次IO
  • B+树:3-4层 = 3-4次IO

B+树 vs B树

  • B+树叶子节点有序链表,范围查询更快

Q3:聚簇索引和非聚簇索引的区别?

特性 聚簇索引 非聚簇索引
数据存储 索引和数据一起 索引和数据分离
叶子节点 存储完整数据行 存储主键值
数量限制 一个表只能一个 可以多个
查询性能 快(无需回表) 慢(需要回表)

Q4:主键索引和唯一索引的区别?

  1. NULL值:主键不允许,唯一索引允许
  2. 数量:主键1个,唯一索引多个
  3. 类型:主键是聚簇索引,唯一索引是二级索引
  4. 性能:主键查询更快(无需回表)

Q5:什么是覆盖索引?

查询的所有列都在索引中,无需回表查询。

优势:减少IO、提升速度、减少锁竞争

sql 复制代码
-- 假设索引: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:什么情况下索引会失效?

  1. 索引列使用函数
  2. 隐式类型转换
  3. LIKE前缀模糊
  4. OR条件有非索引列
  5. 负向查询
  6. 违反最左前缀
  7. 查询结果集过大

Q8:如何设计高效索引?

设计原则

  1. 选择性原则:高区分度列(>0.1)
  2. 最左前缀原则:合理安排顺序
  3. 覆盖索引原则:包含查询列
  4. 索引顺序:等值>范围、高频>低频

Q9:索引越多越好吗?

不是!索引有代价

  • ❌ 占用存储空间
  • ❌ 降低写入性能
  • ❌ 增加维护成本

建议

  • 单表索引不超过5个
  • 定期删除未使用的索引

Q10:主键为什么推荐自增整型?

特性 自增整型 UUID
存储 4/8字节 36字节
插入 顺序插入 随机插入,页分裂
性能 整型比较快 字符串比较慢
分布式 需特殊处理 天然支持

折中方案:雪花算法(BIGINT,有序)


总结

索引核心知识图谱

复制代码
索引本质:空间换时间的数据结构
    ↓
底层结构:B+树(多路平衡查找树)
    ↓
索引分类:
├─ 主键索引(聚簇索引,最快)
├─ 唯一索引(二级索引,较快)
├─ 普通索引(二级索引,常用)
└─ 特殊索引(全文、空间)
    ↓
优化要点:
├─ 避免失效(13种场景)
├─ 合理设计(6大原则)
├─ 覆盖索引(减少回表)
└─ 定期维护(ANALYZE、OPTIMIZE)
    ↓
性能提升:O(n) → O(log n)

索引使用口诀

复制代码
索引设计三原则:选择性、有序性、覆盖性
索引失效三大忌:函数、类型、通配符
索引优化三步走:EXPLAIN、分析、重构

相关推荐
怪兽20144 小时前
Redis过期键的删除策略有哪些?
java·数据库·redis·缓存·面试
骑士雄师6 小时前
使用 IntelliJ IDEA 结合 DBeaver 连接 MySQL 数据库并实现数据增删查改的详细步骤:
数据库·mysql·intellij-idea
b78gb8 小时前
电商秒杀系统设计 Java+MySQL实现高并发库存管理与订单处理
java·开发语言·mysql
奥尔特星云大使8 小时前
CentOS 7 安装 MySQL 8
mysql·centos·mysql 8
lang201509289 小时前
MySQL FIPS模式:安全合规全解析
mysql
一只叫煤球的猫11 小时前
建了索引还是慢?索引失效原因有哪些?这10个坑你踩了几个
后端·mysql·性能优化
呼哧呼哧.11 小时前
Spring的核心思想与注解
数据库·sql·spring
21号 112 小时前
9.Redis 集群(重在理解)
数据库·redis·算法
爬山算法12 小时前
Redis(73)如何处理Redis分布式锁的死锁问题?
数据库·redis·分布式