聚簇索引 vs 非聚簇索引
🟡 中等题目描述
解释聚簇索引和非聚簇索引的区别。什么是回表?如何避免回表?
示例场景
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(50),
age INT,
email VARCHAR(100),
KEY idx_age (age)
);
-- 查询 1:回表
SELECT * FROM users WHERE age = 18;
-- 查询 2:不回表
SELECT id FROM users WHERE age = 18;提示
- 聚簇索引:数据和索引在一起
- 非聚簇索引:索引和数据分离
- 回表:从辅助索引获取主键,再查聚簇索引
解法
参考答案 (3 个标签)
聚簇索引 非聚簇索引 回表
聚簇索引(Clustered Index)
-- InnoDB 的聚簇索引
-- 主键索引就是聚簇索引
CREATE TABLE users (
id INT PRIMARY KEY, -- 聚簇索引
name VARCHAR(50),
age INT
);
-- B+树叶子节点存储:
-- | id (主键) | name | age | 其他列 |特点:
- 数据和索引存储在一起
- 一张表只能有一个聚簇索引
- 通常是主键(如果没有主键,选择唯一非空索引)
- 数据按主键顺序存储
非聚簇索引(Secondary Index)
-- 辅助索引
CREATE INDEX idx_age ON users(age);
-- B+树叶子节点存储:
-- | age (索引列) | id (主键) |特点:
- 索引和数据分离
- 叶子节点存储索引列和主键
- 一张表可以有多个非聚簇索引
- 需要回表才能获取完整数据
回表过程
-- 查询:SELECT * FROM users WHERE age = 18;
-- 步骤:
-- 1. 在 idx_age(非聚簇索引)中查找 age=18
-- 找到:| age=18 | id=5 |
-- 找到:| age=18 | id=10 |
-- 找到:| age=18 | id=15 |
-- 2. 根据主键 id,在聚簇索引中查找完整数据
-- id=5 → | id=5 | name="Alice" | age=18 | ... |
-- id=10 → | id=10 | name="Bob" | age=18 | ... |
-- id=15 → | id=15 | name="Charlie" | age=18 | ... |
-- 总共:1 + 3 = 4 次索引查找图示:
非聚簇索引 idx_age:
┌─────────────────┐
│ age=18, id=5 │──┐
│ age=18, id=10 │──┼───┐
│ age=18, id=15 │──┼───┼───┐
│ age=19, id=20 │ │ │ │
└─────────────────┘ │ │ │
│ │ │
▼ ▼ ▼
聚簇索引(主键索引):
┌──────────────────────┐
│ id=5, name="Alice" │
│ id=10, name="Bob" │
│ id=15, name="Charlie"│
│ id=20, name="David" │
└──────────────────────┘覆盖索引(避免回表)
-- ❌ 回表
SELECT * FROM users WHERE age = 18;
-- ✅ 不回表(覆盖索引)
SELECT id FROM users WHERE age = 18;
-- ✅ 不回表(联合索引)
CREATE INDEX idx_age_name ON users(age, name);
SELECT id, name FROM users WHERE age = 18;原理:
-- 联合索引 idx_age_name:
-- | age | name | id |
-- 查询 SELECT id, name FROM users WHERE age = 18;
-- 索引已包含 id 和 name,无需回表扩展:InnoDB vs MyISAM
索引实现对比
| 特性 | InnoDB | MyISAM |
|---|---|---|
| 聚簇索引 | ✓ | ✗ |
| 非聚簇索引 | ✓ | ✓ |
| 数据存储 | 主键顺序 | 数据文件独立 |
MyISAM 的索引结构
-- MyISAM 所有索引都是非聚簇索引
-- 所有索引的叶子节点都指向数据的物理地址
主键索引:
| id | 数据文件偏移量 |
辅助索引:
| age | 数据文件偏移量 |
-- 不需要回表,直接通过偏移量访问数据
-- 但无法支持范围查询的高效性InnoDB vs MyISAM
| 特性 | InnoDB | MyISAM |
|---|---|---|
| 事务 | ✓ | ✗ |
| 外键 | ✓ | ✗ |
| 锁粒度 | 行锁 | 表锁 |
| 崩溃恢复 | ✓ | ✗ |
| 聚簇索引 | ✓ | ✗ |
| 全文索引 | 5.6+ | ✓ |
索引优化技巧
1. 使用覆盖索引
-- ❌ 回表
SELECT count(*) FROM users WHERE age > 18;
-- ✅ 覆盖索引(优化)
CREATE INDEX idx_age ON users(age);
-- ✅ 联合索引
CREATE INDEX idx_age_id ON users(age, id);
SELECT count(*) FROM users WHERE age > 18;2. 避免 SELECT *
-- ❌ 回表
SELECT * FROM users WHERE age = 18;
-- ✅ 只查询需要的列
SELECT id, name FROM users WHERE age = 18;
-- ✅ 创建联合索引
CREATE INDEX idx_age_name_id ON users(age, name, id);3. 主键设计
-- ❌ 使用过长的主键(辅助索引变大)
CREATE TABLE users (
id VARCHAR(36) PRIMARY KEY, -- UUID
name VARCHAR(50),
age INT
);
-- ✓ 使用自增主键
CREATE TABLE users (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50),
age INT
);
-- ✓ 使用雪花算法(分布式)
CREATE TABLE users (
id BIGINT PRIMARY KEY,
name VARCHAR(50),
age INT
);4. 延迟关联
-- ❌ 先回表再排序
SELECT * FROM users WHERE age > 18 ORDER BY name LIMIT 1000, 10;
-- ✓ 延迟关联(先获取 ID)
SELECT u.* FROM users u
JOIN (
SELECT id FROM users WHERE age > 18 ORDER BY name LIMIT 1000, 10
) AS t ON u.id = t.id;