数据库索引完全指南
目录
什么是数据库索引
索引的基本原理
索引的类型
索引的创建和使用
索引的优缺点
索引失效的场景
索引优化实践
总结
什么是数据库索引
数据库索引是一种数据结构,它提供了快速访问数据库表中数据的路径。就像书籍的目录一样,索引可以帮助数据库快速定位到具体的数据行,而不需要扫描整个表。
形象化理解
想象你要在一本1000页的字典中查找单词"database":
没有索引:你需要从第1页开始逐页翻阅,直到找到为止
有索引:你直接翻到"D"开头的目录页,快速定位到具体页码
索引就是数据库的"目录"。
性能对比示意图
1
2
3
4
5
6
7
8
9
graph TD
A["全表扫描
O(n) 时间复杂度"] --> B["逐行检查每条记录"]
B --> C["找到匹配记录"]
D["使用索引
O(log n) 时间复杂度"] --> E["通过索引快速定位"]
E --> F["直接访问目标记录"]
G["1000万记录对比"] --> H["全表扫描: 最多1000万次比较"]
G --> I["B+树索引: 最多4次比较"]
索引的基本原理
数据存储结构
数据库中的数据通常存储在**页(Page)**中,每个页包含多条记录。
1
2
3
4
5
表数据存储示意:
页1: [记录1, 记录2, 记录3, ...]
页2: [记录101, 记录102, 记录103, ...]
页3: [记录201, 记录202, 记录203, ...]
...
B+树索引原理
大多数数据库使用B+树作为索引的数据结构:
1
2
3
4
5
6
B+树索引结构:
根节点
/ \
内部节点1 内部节点2
/ | \ / | \
叶子节点1 叶子2 叶子3 叶子4 叶子5 叶子6
B+树的特点:
所有数据都存储在叶子节点
叶子节点之间有指针连接,便于范围查询
树的高度较低,减少磁盘I/O次数
支持顺序访问和随机访问
B+树详细结构图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
graph TD
subgraph "B+树索引结构"
Root["根节点
[30, 60]"]
Internal1["内部节点1
[10, 20]"]
Internal2["内部节点2
[40, 50]"]
Internal3["内部节点3
[70, 80]"]
Leaf1["叶子节点1
[1,5,8,10]
→ 数据页1"]
Leaf2["叶子节点2
[15,18,20,25]
→ 数据页2"]
Leaf3["叶子节点3
[35,38,40,42]
→ 数据页3"]
Leaf4["叶子节点4
[45,48,50,55]
→ 数据页4"]
Leaf5["叶子节点5
[65,68,70,75]
→ 数据页5"]
Leaf6["叶子节点6
[78,80,85,90]
→ 数据页6"]
Root --> Internal1
Root --> Internal2
Root --> Internal3
Internal1 --> Leaf1
Internal1 --> Leaf2
Internal2 --> Leaf3
Internal2 --> Leaf4
Internal3 --> Leaf5
Internal3 --> Leaf6
Leaf1 -.-> Leaf2
Leaf2 -.-> Leaf3
Leaf3 -.-> Leaf4
Leaf4 -.-> Leaf5
Leaf5 -.-> Leaf6
end
索引查找过程
从根节点开始
根据比较结果选择分支
逐层向下直到叶子节点
在叶子节点中找到目标数据
查找过程流程图
1
2
3
4
5
6
7
8
9
10
11
flowchart TD
A["查询: SELECT * FROM users WHERE id = 42"] --> B["开始从根节点查找"]
B --> C["根节点: [30, 60]
42 > 30 且 42 < 60"]
C --> D["进入中间节点2: [40, 50]
42 > 40 且 42 < 50"]
D --> E["进入叶子节点3: [35,38,40,42]
找到目标值 42"]
E --> F["通过指针访问数据页3"]
F --> G["返回完整记录数据"]
style A fill:#e1f5fe
style G fill:#c8e6c9
style E fill:#fff3e0
索引的类型
索引分类总览
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
graph TD
A["数据库索引分类"] --> B["按数据结构"]
A --> C["按字段数量"]
A --> D["按功能特性"]
B --> B1["B+树索引
(最常用)"]
B --> B2["哈希索引
(等值查询)"]
B --> B3["全文索引
(文本搜索)"]
C --> C1["单列索引
CREATE INDEX idx_name ON users(name)"]
C --> C2["复合索引
CREATE INDEX idx_name_age ON users(name,age)"]
D --> D1["普通索引
(可重复)"]
D --> D2["唯一索引
(不可重复)"]
D --> D3["主键索引
(主键约束)"]
style B1 fill:#c8e6c9
style C2 fill:#fff3e0
style D3 fill:#ffcdd2
1. 按数据结构分类
B+树索引(最常用)
特点:平衡多路搜索树
适用:等值查询、范围查询、排序
存储引擎:InnoDB、MyISAM
哈希索引
特点:基于哈希表
适用:等值查询
限制:不支持范围查询、排序
存储引擎:Memory
2. 按字段数量分类
单列索引
1
CREATE INDEX idx_name ON users(name);
复合索引(联合索引)
1
CREATE INDEX idx_name_age ON users(name, age);
3. 按功能分类
普通索引
1
CREATE INDEX idx_email ON users(email);
唯一索引
1
CREATE UNIQUE INDEX idx_username ON users(username);
主键索引
1
ALTER TABLE users ADD PRIMARY KEY(id);
全文索引
1
CREATE FULLTEXT INDEX idx_content ON articles(content);
索引的创建和使用
创建索引
建表时创建
1
2
3
4
5
6
7
8
9
CREATE TABLE users (
id INT PRIMARY KEY,
username VARCHAR(50) UNIQUE,
email VARCHAR(100),
age INT,
created_at TIMESTAMP,
INDEX idx_email (email),
INDEX idx_age_created (age, created_at)
);
建表后创建
1
2
3
4
5
6
7
8
-- 普通索引
CREATE INDEX idx_email ON users(email);
-- 唯一索引
CREATE UNIQUE INDEX idx_username ON users(username);
-- 复合索引
CREATE INDEX idx_name_age ON users(name, age);
查看索引
1
2
3
4
5
-- 查看表的所有索引
SHOW INDEX FROM users;
-- 查看索引使用情况
EXPLAIN SELECT * FROM users WHERE email = 'john@example.com';
删除索引
1
DROP INDEX idx_email ON users;
索引的优缺点
优点
1. 提高查询速度
无索引:全表扫描,时间复杂度 O(n)
有索引:B+树查找,时间复杂度 O(log n)
2. 加速排序
1
2
-- 如果name字段有索引,以下查询会很快
SELECT * FROM users ORDER BY name;
3. 提升连接效率
1
2
3
-- 如果user_id有索引,JOIN操作会更快
SELECT * FROM orders o
JOIN users u ON o.user_id = u.id;
4. 加速分组
1
2
-- 如果status有索引,GROUP BY会更快
SELECT status, COUNT(*) FROM orders GROUP BY status;
缺点
1. 占用存储空间
索引需要额外的存储空间
复合索引占用空间更大
2. 降低写操作性能
INSERT:需要维护索引结构
UPDATE:可能需要更新索引
DELETE:需要从索引中删除条目
3. 维护成本
数据变更时需要同步更新索引
索引越多,维护成本越高
索引失效的场景
索引失效场景总览
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
graph LR
A["索引失效场景"] --> B["函数操作
WHERE UPPER(name) = 'JOHN'"]
A --> C["类型转换
WHERE phone = 123456"]
A --> D["前导模糊查询
WHERE name LIKE '%john'"]
A --> E["OR连接条件
WHERE name='john' OR age=25"]
A --> F["非最左匹配
复合索引(name,age,city)
WHERE age=25"]
A --> G["不等于操作
WHERE status != 'active'"]
A --> H["NULL值判断
WHERE email IS NULL"]
style B fill:#ffcdd2
style C fill:#ffcdd2
style D fill:#ffcdd2
style E fill:#ffcdd2
style F fill:#ffcdd2
style G fill:#ffcdd2
style H fill:#ffcdd2
1. 函数操作
1
2
3
4
5
-- 索引失效
SELECT * FROM users WHERE UPPER(name) = 'JOHN';
-- 正确做法
SELECT * FROM users WHERE name = 'john';
2. 类型转换
1
2
3
4
5
-- 索引失效(假设phone是VARCHAR类型)
SELECT * FROM users WHERE phone = 12345678901;
-- 正确做法
SELECT * FROM users WHERE phone = '12345678901';
3. 前导模糊查询
1
2
3
4
5
-- 索引失效
SELECT * FROM users WHERE name LIKE '%john';
-- 可以使用索引
SELECT * FROM users WHERE name LIKE 'john%';
4. OR连接的条件
1
2
3
4
5
6
7
-- 如果age没有索引,整个查询无法使用索引
SELECT * FROM users WHERE name = 'john' OR age = 25;
-- 改写为UNION(如果两个字段都有索引)
SELECT * FROM users WHERE name = 'john'
UNION
SELECT * FROM users WHERE age = 25;
5. 复合索引的非最左匹配
1
2
3
4
5
6
7
8
9
-- 假设有复合索引 (name, age, city)
-- 可以使用索引
SELECT * FROM users WHERE name = 'john';
SELECT * FROM users WHERE name = 'john' AND age = 25;
-- 索引失效
SELECT * FROM users WHERE age = 25;
SELECT * FROM users WHERE city = 'beijing';
6. 不等于操作
1
2
3
4
5
-- 可能导致索引失效
SELECT * FROM users WHERE status != 'active';
-- 建议改写
SELECT * FROM users WHERE status IN ('inactive', 'deleted');
7. NULL值判断
1
2
3
-- 索引效果有限
SELECT * FROM users WHERE email IS NULL;
SELECT * FROM users WHERE email IS NOT NULL;
索引优化实践
1. 选择合适的字段创建索引
高选择性字段
1
2
3
4
5
-- 好的选择:用户邮箱(几乎唯一)
CREATE INDEX idx_email ON users(email);
-- 差的选择:性别字段(只有几个值)
-- 不建议:CREATE INDEX idx_gender ON users(gender);
经常用于查询条件的字段
1
2
3
4
5
-- 经常用于WHERE子句
CREATE INDEX idx_status ON orders(status);
-- 经常用于JOIN
CREATE INDEX idx_user_id ON orders(user_id);
2. 复合索引设计原则
最左前缀原则
1
2
3
4
5
6
7
8
9
-- 索引:(name, age, city)
-- 可以使用索引的查询:
SELECT * FROM users WHERE name = 'john'; -- ✓
SELECT * FROM users WHERE name = 'john' AND age = 25; -- ✓
SELECT * FROM users WHERE name = 'john' AND city = 'bj'; -- ✓(但不是最优)
-- 不能使用索引的查询:
SELECT * FROM users WHERE age = 25; -- ✗
SELECT * FROM users WHERE city = 'beijing'; -- ✗
字段顺序优化
1
2
3
4
-- 原则:选择性高的字段放前面,经常一起查询的字段放一起
-- 假设查询:WHERE status = 'active' AND created_at > '2023-01-01'
-- 如果status选择性更高:
CREATE INDEX idx_status_created ON orders(status, created_at);
3. 覆盖索引优化
1
2
3
4
5
6
-- 普通查询:需要回表
CREATE INDEX idx_user_id ON orders(user_id);
SELECT order_id, total_amount FROM orders WHERE user_id = 123;
-- 覆盖索引:无需回表
CREATE INDEX idx_user_id_cover ON orders(user_id, order_id, total_amount);
4. 分页查询优化
1
2
3
4
5
6
7
8
-- 深分页问题
SELECT * FROM users ORDER BY created_at LIMIT 100000, 20;
-- 优化方案:使用索引字段
SELECT * FROM users
WHERE created_at > '2023-01-01 10:30:00'
ORDER BY created_at
LIMIT 20;
5. 索引监控和维护
查看索引使用情况
1
2
3
4
5
-- MySQL
SELECT * FROM sys.schema_unused_indexes;
-- 查看索引统计
SHOW INDEX FROM users;
定期重建索引
1
2
3
4
5
-- 重建索引(MySQL)
ALTER TABLE users DROP INDEX idx_email, ADD INDEX idx_email(email);
-- 或者
OPTIMIZE TABLE users;
实际案例分析
案例1:慢查询优化
问题查询:
1
2
3
4
SELECT * FROM orders
WHERE status = 'pending'
AND created_at >= '2023-01-01'
AND user_id IN (1,2,3,4,5);
分析过程:
使用 EXPLAIN 分析执行计划
发现全表扫描,耗时很长
分析查询条件的选择性
优化方案:
1
2
3
4
5
-- 创建复合索引
CREATE INDEX idx_status_created_user ON orders(status, created_at, user_id);
-- 或者根据实际查询模式创建
CREATE INDEX idx_user_status_created ON orders(user_id, status, created_at);
案例2:复合索引设计
业务场景:
用户表经常按地区、年龄、状态查询
查询模式分析:
WHERE region = ‘beijing’
WHERE region = ‘beijing’ AND age BETWEEN 20 AND 30
WHERE region = ‘beijing’ AND status = ‘active’
索引设计:
1
2
3
4
5
6
-- 方案1:按查询频率和选择性
CREATE INDEX idx_region_age_status ON users(region, age, status);
-- 方案2:如果经常只按region和status查询
CREATE INDEX idx_region_status ON users(region, status);
CREATE INDEX idx_region_age ON users(region, age);
总结
核心要点
索引本质:用空间换时间的数据结构,类似书籍目录
主要原理:B+树结构,减少磁盘I/O,提高查询效率
使用场景:频繁查询、排序、连接的字段
设计原则:
选择性高的字段
遵循最左前缀原则
考虑覆盖索引
避免过多索引
最佳实践清单
为经常出现在WHERE子句的字段创建索引
为经常用于JOIN的字段创建索引
复合索引遵循最左前缀原则
避免在索引字段上使用函数
定期监控和优化索引使用情况
平衡查询性能和存储空间
考虑业务场景,不要盲目创建索引
记住这句话
索引不是万能的,但没有索引是万万不能的。合理使用索引,让你的数据库查询飞起来!
这份指南涵盖了数据库索引的方方面面,建议结合实际项目练习,加深理解。