MySQL索引底层原理:从B+树到聚簇索引
【摘要】 MySQL索引底层原理:从B+树到聚簇索引本文深入剖析MySQL索引的底层实现原理,帮助你理解为什么索引能加速查询,以及如何设计高效的索引。 为什么索引能加速查询?没有索引时,MySQL需要全表扫描(Full Table Scan),时间复杂度O(n)。索引通过有序数据结构,将查询复杂度降低到O(log n)。 B+树:MySQL索引的核心 为什么选择B+树?数据结构优点缺点适用场景哈希表...
MySQL索引底层原理:从B+树到聚簇索引
本文深入剖析MySQL索引的底层实现原理,帮助你理解为什么索引能加速查询,以及如何设计高效的索引。
为什么索引能加速查询?
没有索引时,MySQL需要全表扫描(Full Table Scan),时间复杂度O(n)。索引通过有序数据结构,将查询复杂度降低到O(log n)。
B+树:MySQL索引的核心
为什么选择B+树?
| 数据结构 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 哈希表 | O(1)查询 | 不支持范围查询、无序 | 等值查询 |
| 二叉搜索树 | 有序 | 退化成链表、树高过大 | 内存数据 |
| AVL树 | 平衡 | 旋转频繁、树高较大 | 内存数据 |
| B树 | 矮胖、减少磁盘IO | 节点存储数据、范围查询慢 | 文件系统 |
| B+树 | 矮胖、范围查询快、节点只存索引 | - | 数据库索引 |
B+树结构详解
┌───┐
│ 5 │
└─┬─┘
┌───────┴───────┐
↓ ↓
┌───┐ ┌───┐
│ 2 │ │ 7 │
└─┬─┘ └─┬─┘
┌────┴────┐ ┌────┴────┐
↓ ↓ ↓ ↓
┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐
│1│ │2│ │3│ │4│ │5│ │6│ │7│ │8│
└─┘ └─┘ └─┘ └─┘ └─┘ └─┘ └─┘ └─┘
叶子节点形成双向链表,支持范围查询
B+树特点:
- 非叶子节点只存储索引,不存储数据
- 叶子节点存储所有数据,形成双向链表
- 树高稳定,3-4层可存储千万级数据
- 范围查询高效,遍历链表即可
计算B+树能存储多少数据
假设:
- 索引字段为BIGINT(8字节)
- 每个指针6字节
- 页大小16KB
单个节点可存储指针数 = 16KB / (8 + 6) ≈ 1170
树高3层可存储数据量 = 1170 × 1170 × 1170 ≈ 1.6亿
聚簇索引与非聚簇索引
聚簇索引(Clustered Index)
聚簇索引:数据行与索引存储在一起
┌─────────────────────────────────┐
│ 主键索引 │ 数据行 │ 数据行 │
│ 1 │ name=A │ age=20 │
│ 2 │ name=B │ age=25 │
│ 3 │ name=C │ age=30 │
└─────────────────────────────────┘
特点:
- 每张表只能有一个聚簇索引
- 数据按主键顺序存储
- 查询主键直接获取数据,无需回表
非聚簇索引(Secondary Index)
非聚簇索引:索引与数据分离
索引页: 数据页:
┌─────────┐ ┌─────────┐
│ 1 ────→│──────────────→│ name=A │
│ 2 ────→│──────────────→│ name=B │
│ 3 ────→│──────────────→│ name=C │
└─────────┘ └─────────┘
特点:
- 可以有多个非聚簇索引
- 索引节点存储主键值
- 查询需要"回表"获取完整数据
回表查询示例
-- 表结构
CREATE TABLE users (
id BIGINT PRIMARY KEY,
name VARCHAR(50),
age INT,
INDEX idx_name (name)
);
-- 执行计划分析
EXPLAIN SELECT * FROM users WHERE name = '张三';
-- 输出:
-- type: ref
-- key: idx_name
-- Extra: Using index condition
-- 注意:这里需要回表查询age字段
索引下推(ICP)
问题:回表次数过多
-- 查询条件包含索引列和非索引列
SELECT * FROM users WHERE name LIKE '张%' AND age > 20;
传统方式:
- 在idx_name索引中查找name LIKE ‘张%’
- 回表获取所有匹配的行
- 在Server层过滤age > 20
索引下推(ICP):
- 在idx_name索引中同时过滤name LIKE ‘张%’ AND age > 20
- 只回表满足条件的行
- 减少回表次数
开启ICP
-- MySQL 5.6+ 默认开启
SET optimizer_switch = 'index_condition_pushdown=on';
-- 查看是否使用ICP
EXPLAIN SELECT * FROM users WHERE name LIKE '张%' AND age > 20;
-- Extra: Using index condition
覆盖索引
定义
查询的字段全部在索引中,无需回表。
-- 创建覆盖索引
CREATE INDEX idx_name_age ON users(name, age);
-- 覆盖索引查询
SELECT name, age FROM users WHERE name = '张三';
-- Extra: Using index
覆盖索引的优势
普通索引查询:
索引查找 → 回表 → 获取数据
覆盖索引查询:
索引查找 → 直接返回数据
联合索引与最左前缀原则
联合索引结构
联合索引 (name, age):
┌─────────┬───────┐
│ name │ age │
├─────────┼───────┤
│ 张三 │ 20 │
│ 张三 │ 25 │
│ 李四 │ 30 │
│ 王五 │ 35 │
└─────────┴───────┘
最左前缀原则
-- 创建联合索引
CREATE INDEX idx_name_age ON users(name, age);
-- ✅ 可以使用索引
SELECT * FROM users WHERE name = '张三';
SELECT * FROM users WHERE name = '张三' AND age = 20;
-- ❌ 无法使用索引
SELECT * FROM users WHERE age = 20;
-- ✅ 可以使用索引(范围查询)
SELECT * FROM users WHERE name LIKE '张%' AND age > 20;
索引失效场景
-- 1. 对索引字段使用函数
SELECT * FROM users WHERE YEAR(create_time) = 2024;
-- 正确写法:
SELECT * FROM users WHERE create_time >= '2024-01-01' AND create_time < '2025-01-01';
-- 2. 隐式类型转换
SELECT * FROM users WHERE name = 123; -- name是VARCHAR
-- 正确写法:
SELECT * FROM users WHERE name = '123';
-- 3. 使用OR条件
SELECT * FROM users WHERE name = '张三' OR age = 20;
-- 正确写法:使用UNION
SELECT * FROM users WHERE name = '张三'
UNION
SELECT * FROM users WHERE age = 20;
-- 4. LIKE以通配符开头
SELECT * FROM users WHERE name LIKE '%张三';
-- 正确写法:使用前缀匹配
SELECT * FROM users WHERE name LIKE '张三%';
实战:索引设计最佳实践
1. 选择合适的索引列
-- 高选择性列优先
-- 低选择性列(如性别)不适合单独建索引
SELECT COUNT(DISTINCT gender) / COUNT(*) FROM users; -- 选择性低
SELECT COUNT(DISTINCT user_id) / COUNT(*) FROM users; -- 选择性高
2. 索引列顺序
-- 等值查询列在前,范围查询列在后
-- 适合:WHERE status = 'active' AND create_time > '2024-01-01'
CREATE INDEX idx_status_time ON orders(status, create_time);
3. 避免冗余索引
-- 冗余索引:idx_name 和 idx_name_age 存在包含关系
CREATE INDEX idx_name ON users(name);
CREATE INDEX idx_name_age ON users(name, age);
-- 删除冗余索引
DROP INDEX idx_name ON users;
4. 监控索引使用
-- 查看索引使用情况
SELECT * FROM sys.schema_unused_indexes;
-- 查看索引大小
SELECT table_name, index_name,
ROUND(stat_value * @@innodb_page_size / 1024 / 1024, 2) AS size_mb
FROM mysql.innodb_index_stats
WHERE stat_name = 'size';
总结
| 概念 | 要点 |
|---|---|
| B+树 | 非叶子节点存索引,叶子节点存数据,支持范围查询 |
| 聚簇索引 | 数据与索引在一起,每表只有一个 |
| 非聚簇索引 | 索引与数据分离,需要回表 |
| 覆盖索引 | 查询字段都在索引中,无需回表 |
| 索引下推 | 在索引层过滤数据,减少回表 |
| 最左前缀 | 联合索引从左到右匹配 |
索引设计原则:
- 选择性高的列优先
- 等值查询列在前,范围查询列在后
- 避免冗余索引
- 监控索引使用情况
- 定期分析慢查询
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)