索引原理与应用
最后更新: 2026-01-22
作者: MySQL Team
页面目录
第五章:索引原理与应用
5.1 索引概述
什么是索引
索引是数据库中一种特殊的数据结构,用于加速数据检索,就像书籍的目录一样。
索引的优点:
- 加速数据查询,减少扫描行数
- 唯一索引保证数据唯一性
- 加速 ORDER BY 和 GROUP BY
索引的缺点:
- 占用磁盘空间
- 增加写操作开销(INSERT/UPDATE/DELETE)
- 过多索引反而降低性能
5.2 索引数据结构
B-Tree 索引(默认)
B-Tree 结构示意:
[30]
/ \
[10, 20] [40, 50]
/ \ \ / \ \
[5] [15] [25] [35] [45] [55]
特点:
- 平衡多叉树
- 所有叶子节点在同一层
- 节点按key排序
- 查询复杂度 O(log n)
B-Tree 适用场景:
-- 全值匹配
SELECT * FROM users WHERE email = 'test@example.com';
-- 最左前缀匹配
SELECT * FROM users WHERE name = '张三'; -- 使用 idx_name
SELECT * FROM users WHERE name = '张三' AND age = 25; -- 使用 idx_name_age
-- 范围查询
SELECT * FROM users WHERE age BETWEEN 18 AND 30;
-- ORDER BY 优化
SELECT * FROM users ORDER BY name; -- 使用索引避免排序
Hash 索引
特点:
- 基于哈希表实现
- 查询复杂度 O(1)
- 仅支持等值查询
- 无法排序
- 无法范围查询
-- MEMORY 存储引擎使用 HASH 索引
CREATE TABLE hash_table (
id INT,
name VARCHAR(50),
INDEX idx_name (name) USING HASH
) ENGINE=MEMORY;
聚簇索引 vs 辅助索引
| 类型 | 说明 | 特点 |
|---|---|---|
| 聚簇索引 | 主键索引,叶子节点存储完整行数据 | 每个表只能有一个 |
| 辅助索引 | 非主键索引,叶子节点存储主键值 | 可以有多个 |
聚簇索引结构:
┌─────────────────────────────────┐
│ 主键值 │ 行数据 │
├─────────────────────────────────┤
│ 1 │ id=1, name=张三, age=25 │
│ 3 │ id=3, name=李四, age=30 │
│ 5 │ id=5, name=王五, age=28 │
└─────────────────────────────────┘
辅助索引结构(以 name 为例):
┌─────────────────┐
│ name │ 主键值 │
├─────────────────┤
│ 张三 │ 1 │
│ 王五 │ 5 │
│ 李四 │ 3 │
└─────────────────┘
覆盖索引
-- 查询只需索引字段,无需回表
CREATE INDEX idx_name_age ON users(name, age);
-- 覆盖索引查询
SELECT name, age FROM users WHERE name = '张三'; -- 不需要回表
-- 非覆盖索引(需要回表)
SELECT * FROM users WHERE name = '张三'; -- 需要回表获取其他字段
5.3 索引分类
主键索引
-- 创建主键索引
ALTER TABLE users ADD PRIMARY KEY (id);
-- 删除主键索引
ALTER TABLE users DROP PRIMARY KEY;
唯一索引
-- 创建唯一索引
CREATE UNIQUE INDEX uk_email ON users(email);
-- 表创建时定义
CREATE TABLE users (
id INT PRIMARY KEY,
email VARCHAR(100) UNIQUE
);
普通索引
-- 创建普通索引
CREATE INDEX idx_name ON users(name);
-- 或
ALTER TABLE users ADD INDEX idx_name (name);
全文索引
-- 创建全文索引
CREATE FULLTEXT INDEX ft_content ON articles(title, content);
-- 搜索使用 MATCH ... AGAINST
SELECT * FROM articles
WHERE MATCH(title, content) AGAINST('MySQL 优化');
组合索引
-- 创建组合索引(最左前缀原则)
CREATE INDEX idx_name_age_status ON users(name, age, status);
-- 使用情况分析:
✅ SELECT * FROM users WHERE name = '张三'; -- 使用索引
✅ SELECT * FROM users WHERE name = '张三' AND age = 25; -- 使用索引
✅ SELECT * FROM users WHERE name = '张三' AND age = 25 AND status = 1; -- 使用索引
❌ SELECT * FROM users WHERE age = 25; -- 不使用索引
❌ SELECT * FROM users WHERE status = 1; -- 不使用索引
⚠️ SELECT * FROM users WHERE name = '张三' AND status = 1; -- 只使用 name 部分
5.4 索引创建原则
应该创建索引的场景
| 场景 | 说明 | 示例 |
|---|---|---|
| WHERE 条件 | 经常作为查询条件的字段 | WHERE status = 1 |
| JOIN 连接 | 经常用于连接的字段 | ON orders.user_id = users.id |
| ORDER BY | 经常排序的字段 | ORDER BY created_at DESC |
| 高选择性 | 选择性高的字段(区分度大) | 身份证号 > 性别 |
| 主键/外键 | 表关联字段 | FOREIGN KEY (user_id) |
不应该创建索引的场景
| 场景 | 说明 | 示例 |
|---|---|---|
| 低选择性 | 重复值多 | 性别、状态 |
| 频繁更新 | 更新频繁的字段 | 最后修改时间 |
| 文本过长 | TEXT、BLOB 字段 | content、description |
| 少量数据 | 表数据很少 | 测试表 |
| 不用于查询 | 不出现在 WHERE 中 | 存储过程的变量 |
索引设计建议
-- 1. 优先考虑 WHERE、JOIN、ORDER BY 使用的字段
-- 2. 使用短索引(减少 I/O)
CREATE INDEX idx_name ON users(name(20)); -- 只索引前20个字符
-- 3. 组合索引考虑列顺序(选择性高的放前面)
-- 4. 避免过多索引(增加维护成本)
-- 5. 定期分析表优化索引
ANALYZE TABLE users;
5.5 索引管理
查看索引
-- 查看表的所有索引
SHOW INDEX FROM users;
-- 或
SHOW CREATE TABLE users\G
创建索引
-- 方式1:CREATE INDEX
CREATE INDEX idx_name ON users(name);
CREATE UNIQUE INDEX uk_email ON users(email);
CREATE INDEX idx_composite ON users(name, age, status);
-- 方式2:ALTER TABLE
ALTER TABLE users ADD INDEX idx_name (name);
ALTER TABLE users ADD UNIQUE INDEX uk_email (email);
ALTER TABLE users ADD INDEX idx_composite (name, age);
-- 方式3:创建表时
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(50),
email VARCHAR(100),
INDEX idx_name (name),
UNIQUE INDEX uk_email (email)
);
删除索引
DROP INDEX idx_name ON users;
DROP INDEX uk_email ON users;
-- 或使用 ALTER TABLE
ALTER TABLE users DROP INDEX idx_name;
ALTER TABLE users DROP INDEX uk_email;
重命名索引
-- MySQL 不支持直接重命名,需要先删除再创建
DROP INDEX old_name ON users;
CREATE INDEX new_name ON users(name);
5.6 索引优化技巧
前缀索引
-- 对长字符串创建前缀索引
ALTER TABLE articles ADD INDEX idx_title (title(50));
ALTER TABLE articles ADD INDEX idx_content (content(100));
-- 选择合适的前缀长度(区分度 > 0.9 较好)
SELECT
COUNT(DISTINCT LEFT(name, 10)) / COUNT(*) AS selectivity_10,
COUNT(DISTINCT LEFT(name, 20)) / COUNT(*) AS selectivity_20,
COUNT(DISTINCT LEFT(name, 30)) / COUNT(*) AS selectivity_30
FROM users;
索引下推(ICP)
-- 优化器自动使用 ICP
SET optimizer_switch = 'index_condition_pushdown=on';
-- 示例:查询 name LIKE '张%' AND age = 25
-- ICP 优化:在索引中先过滤 age,再回表
SELECT * FROM users
WHERE name LIKE '张%' AND age = 25;
MRR(Multi-Range Read)优化
-- 优化范围查询和随机 I/O
SET optimizer_switch = 'mrr=on,mrr_cost_based=on';
-- 示例
SELECT * FROM users WHERE id IN (100, 50, 200);
5.7 SQL 提示(Optimizer Hints)
强制使用索引
-- 强制使用索引
SELECT * FROM users USE INDEX (idx_name) WHERE name = '张三';
-- 忽略索引
SELECT * FROM users IGNORE INDEX (idx_name) WHERE name = '张三';
-- 强制使用索引(MySQL 8.0+)
SELECT * FROM users FORCE INDEX (idx_name) WHERE name = '张三';
优化器提示
-- 禁止某种访问类型
SELECT * FROM users STRAIGHT_JOIN ... WHERE ...;
-- 指定连接顺序
SELECT /*+ JOIN_FIXED_ORDER() */ ...;
-- 指定 join_buffer_size
SELECT /*+ SET_VAR(join_buffer_size = 256M) */ ... FROM t1 JOIN t2;
5.8 索引失效场景
-- 1. 使用函数或运算
❌ SELECT * FROM users WHERE YEAR(created_at) = 2024;
✅ SELECT * FROM users WHERE created_at >= '2024-01-01' AND created_at < '2025-01-01';
-- 2. 类型转换
❌ SELECT * FROM users WHERE id = '1'; -- id 是 INT,字符串 '1'
✅ SELECT * FROM users WHERE id = 1;
-- 3. LIKE 开头通配符
❌ SELECT * FROM users WHERE name LIKE '%三%';
❌ SELECT * FROM users WHERE name LIKE '%三';
✅ SELECT * FROM users WHERE name LIKE '三%';
-- 4. OR 条件
❌ SELECT * FROM users WHERE name = '张三' OR age = 25;
✅ SELECT * FROM users WHERE name = '张三' UNION SELECT * FROM users WHERE age = 25;
-- 5. NOT EQUAL
❌ SELECT * FROM users WHERE status != 0;
✅ 考虑其他方案或使用覆盖索引
-- 6. IS NULL / IS NOT NULL
⚠️ 可能不使用索引(取决于数据分布)
-- 7. 组合索引不使用最左前缀
❌ SELECT * FROM users WHERE age = 25 AND status = 1; -- 未使用 name
5.9 索引分析与维护
ANALYZE TABLE
-- 分析表(更新索引统计信息)
ANALYZE TABLE users;
CHECK TABLE
-- 检查表是否有错误
CHECK TABLE users;
OPTIMIZE TABLE
-- 优化表(整理碎片、回收空间)
OPTIMIZE TABLE users;
CHECKSUM TABLE
-- 校验表数据一致性
CHECKSUM TABLE users;
5.10 索引实战案例
案例1:用户表优化
-- 创建用户表并添加索引
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL,
phone VARCHAR(20),
status TINYINT DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_username (username),
UNIQUE KEY uk_email (email),
INDEX idx_status (status),
INDEX idx_created (created_at),
INDEX idx_phone (phone)
);
-- 查询优化示例
-- 场景1:登录查询
SELECT * FROM users WHERE username = 'xxx' OR email = 'xxx';
-- 建议:添加组合索引
ALTER TABLE users ADD INDEX idx_login (username, email);
-- 场景2:状态筛选+排序
SELECT * FROM users WHERE status = 1 ORDER BY created_at DESC LIMIT 100;
-- 建议:添加组合索引
ALTER TABLE users ADD INDEX idx_status_created (status, created_at DESC);
案例2:订单表优化
CREATE TABLE orders (
id INT PRIMARY KEY AUTO_INCREMENT,
order_no VARCHAR(32) NOT NULL,
user_id INT NOT NULL,
status TINYINT DEFAULT 0,
total_amount DECIMAL(10,2),
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_order_no (order_no),
INDEX idx_user_id (user_id),
INDEX idx_status (status),
INDEX idx_created (created_at),
INDEX idx_composite (user_id, status, created_at)
);
-- 查询优化:用户订单列表
SELECT * FROM orders
WHERE user_id = 123
ORDER BY created_at DESC
LIMIT 20;
-- 使用 idx_composite 索引
-- 查询优化:待处理订单统计
SELECT COUNT(*) FROM orders WHERE status = 0;
-- 使用 idx_status 索引
5.11 本章小结
✅ 理解 B-Tree 索引原理和聚簇/辅助索引区别
✅ 掌握各类索引创建方法:主键、唯一、普通、全文、组合
✅ 遵循索引设计原则,避免索引滥用
✅ 理解最左前缀原则和覆盖索引
✅ 识别索引失效场景并优化查询
✅ 定期维护索引:ANALYZE、OPTIMIZE
📖 下章预告:视图与存储过程