索引基础
一、索引分类
1.1 按数据结构分类
| 索引类型 | 说明 | 适用场景 |
|---|---|---|
| B+Tree 索引 | InnoDB 默认,支持范围查询和排序 | 绝大多数场景 |
| Hash 索引 | Memory 引擎默认,等值查询极快 | 等值查询、不支持范围 |
| Full-Text 索引 | 全文检索 | 大文本模糊搜索 |
| R-Tree 索引 | 空间索引 | 地理数据(MyISAM) |
1.2 按逻辑功能分类
sql
-- 主键索引(PRIMARY KEY)
-- 唯一且非空,一张表只能有一个
CREATE TABLE t1 (id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50));
-- 唯一索引(UNIQUE)
-- 允许 NULL(可多个NULL),保证列值唯一
CREATE TABLE t2 (
id INT PRIMARY KEY,
phone VARCHAR(20) UNIQUE,
email VARCHAR(100),
UNIQUE KEY uk_email (email)
);
-- 普通索引(INDEX/KEY)
-- 仅加速查询,无唯一性约束
CREATE TABLE t3 (
id INT PRIMARY KEY,
name VARCHAR(50),
INDEX idx_name (name)
);
-- 联合索引(复合索引)
-- 多个字段组合成一个索引,遵循最左前缀原则
CREATE TABLE t4 (
id INT PRIMARY KEY,
a INT,
b INT,
c INT,
INDEX idx_a_b_c (a, b, c)
);
1.3 其他分类方式
| 维度 | 分类 |
|---|---|
| 数据存储 | 聚簇索引(Clustered)、非聚簇索引(Secondary) |
| 索引数量 | 单列索引、联合索引 |
| 是否唯一 | 唯一索引、普通索引 |
二、索引操作语法
2.1 创建索引
sql
-- 建表时创建
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50),
email VARCHAR(100),
dept_id INT,
INDEX idx_name (name),
UNIQUE INDEX idx_email (email),
INDEX idx_dept_name (dept_id, name)
);
-- 建表后创建
CREATE INDEX idx_name ON users(name);
CREATE UNIQUE INDEX idx_email ON users(email);
CREATE INDEX idx_dept_name ON users(dept_id, name);
-- 用 ALTER 创建
ALTER TABLE users ADD INDEX idx_name (name);
ALTER TABLE users ADD UNIQUE INDEX idx_email (email);
ALTER TABLE users ADD INDEX idx_dept_name (dept_id, name);
2.2 删除索引
sql
DROP INDEX idx_name ON users;
ALTER TABLE users DROP INDEX idx_name;
2.3 查看索引
sql
-- 查看表上的所有索引
SHOW INDEX FROM users;
-- 或使用
SHOW CREATE TABLE users;
三、索引适用场景
3.1 适合创建索引的场景
sql
-- 1. WHERE 条件字段
-- 频繁作为查询条件的字段应该加索引
SELECT * FROM orders WHERE status = 1; -- status 建索引
-- 2. JOIN 关联字段
-- 多表 JOIN 时,ON 后面的关联字段应建索引
SELECT * FROM orders o
JOIN users u ON o.user_id = u.id; -- o.user_id, u.id 建索引
-- 3. ORDER BY 排序字段
-- 利用索引有序性避免文件排序
SELECT * FROM products ORDER BY create_time DESC; -- create_time 建索引
-- 4. 覆盖索引字段
-- 如果查询的字段都在索引中,无需回表
-- 建联合索引 idx_a_b,查询 a,b 无需回表
SELECT a, b FROM t WHERE a = 1; -- 覆盖索引
-- 5. 分组字段
SELECT dept_id, COUNT(*) FROM employee GROUP BY dept_id; -- dept_id 建索引
3.2 不适合创建索引的场景
sql
-- 1. 数据量小的表(几千行以内),全表扫描更快
-- 2. 频繁更新的字段,索引维护开销大
-- 3. 区分度低的字段(如 gender:男/女),过滤效果差
-- 4. 长文本字段(如 TEXT、超长 VARCHAR),索引体积大且效率低
3.3 索引使用禁忌
| 禁忌 | 原因 | 示例 |
|---|---|---|
| 过多索引 | INSERT/UPDATE/DELETE 时维护成本高 | 单表不超过5个索引 |
| 冗余索引 | 浪费空间,降低写入性能 | idx_a_b 和 idx_a 同时存在 |
| 长字段索引 | 索引体积大、B+树层级深 | TEXT、VARCHAR(500) 直接建索引 |
四、实战案例
案例1:查询性能对比
sql
-- 准备大量数据
CREATE TABLE t_user (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50),
age INT,
email VARCHAR(100),
create_time DATETIME
);
-- 插入 100 万条数据(存储过程)
DELIMITER $$
CREATE PROCEDURE init_data()
BEGIN
DECLARE i INT DEFAULT 1;
WHILE i <= 1000000 DO
INSERT INTO t_user(name, age, email, create_time)
VALUES (CONCAT('user', i), FLOOR(RAND()*100),
CONCAT(i, '@email.com'), NOW());
SET i = i + 1;
END WHILE;
END$$
DELIMITER ;
CALL init_data();
-- 无索引查询:全表扫描
SELECT * FROM t_user WHERE name = 'user500000'; -- 耗时 800ms+
-- 创建索引后
CREATE INDEX idx_name ON t_user(name);
-- 再次查询:索引查找
SELECT * FROM t_user WHERE name = 'user500000'; -- 耗时 < 10ms
案例2:联合索引设计
sql
-- 业务查询模式
-- 场景1:按状态和创建时间查订单
SELECT * FROM orders WHERE status = 1 ORDER BY create_time DESC;
-- 场景2:按用户查订单
SELECT * FROM orders WHERE user_id = 100;
-- 场景3:按状态、用户、时间组合查
SELECT * FROM orders
WHERE status = 1 AND user_id = 100
ORDER BY create_time DESC;
-- 索引设计方案
-- 根据查询频率,优先为高频查询设计索引
CREATE INDEX idx_status_create ON orders(status, create_time); -- 覆盖场景1
CREATE INDEX idx_user_id ON orders(user_id); -- 覆盖场景2
-- 场景3可以通过 idx_status_create 部分利用
案例3:覆盖索引优化
sql
-- 原始查询:需要回表
SELECT id, name, age FROM t_user WHERE name = 'user500000';
-- 索引 idx_name 找到 id,再回表查 age
-- 优化:创建覆盖索引
CREATE INDEX idx_name_age ON t_user(name, age);
-- 再次查询:无需回表,直接在索引中拿到 name 和 age
SELECT id, name, age FROM t_user WHERE name = 'user500000';
案例4:索引下推(ICP)
sql
-- MySQL 5.6+ 支持索引下推
-- 联合索引 idx_name_age
SELECT * FROM t_user WHERE name LIKE 'user5%' AND age = 30;
-- 没有ICP:通过 name 条件找到主键,回表后再过滤 age
-- 启用ICP:在索引遍历时就同时过滤 age,减少回表次数
-- 默认启用,无需特殊配置
五、常见问题
5.1 为什么不建议在每个字段上都建索引?
sql
-- 写入变慢:INSERT 一条记录需要同时维护所有索引的 B+ 树
-- 占用空间:每个索引都是一棵 B+ 树
-- 优化器困扰:过多的索引会让优化器选错执行计划
5.2 如何查看索引是否被使用?
sql
-- 查看索引使用情况
SHOW STATUS LIKE 'Handler_read%';
-- Handler_read_key:索引读取次数(越高越好)
-- Handler_read_rnd_next:全表扫描次数(越低越好)
-- 查看未使用的索引
SELECT * FROM sys.schema_unused_indexes;
5.3 索引选择性是什么?
sql
-- 选择性 = COUNT(DISTINCT col) / COUNT(*)
-- 选择性越高(接近1),索引效果越好
-- 选择性 < 20% 的字段不太适合单独建索引
-- 计算选择性
SELECT COUNT(DISTINCT name) / COUNT(*) AS selectivity FROM t_user;
总结
| 分类 | 要点 |
|---|---|
| 索引类型 | 主键/唯一/普通/联合,按需选择 |
| 创建时机 | 建表时规划,根据查询模式设计 |
| 适用场景 | WHERE/JOIN/ORDER BY/GROUP BY 字段 |
| 禁忌 | 不过度索引、避免冗余、长字段慎用 |
| 优化要点 | 覆盖索引、索引下推、高选择性字段优先 |
索引问题
CREATE INDEX idx_status_create ON orders(status, create_time);和
CREATE INDEX idx_status ON orders(status);CREATE INDEX idx_create ON orders( create_time);
有什么区别,两个字段一起建立索引有什么好处和坏处
一、两种索引写法核心区别
语句1:联合索引(复合索引)
sql
CREATE INDEX idx_status_create ON orders(status, create_time);
语句2:两个单列索引
sql
CREATE INDEX idx_status ON orders(status);
CREATE INDEX idx_create ON orders(create_time);
二、联合索引 vs 单列索引 差异、优缺点
1. 查询命中规则(关键:最左前缀原则)
(1)联合索引 (status, create_time)
能命中索引的场景:
- 只用
status查询 status + create_time组合查询status模糊右匹配(如status LIKE 'xxx%')
无法完整命中:
- 仅单独用
create_time查询 → 不走这个联合索引
(2)两个单列索引 status、create_time
- 单独查
status:命中对应单列索引 - 单独查
create_time:命中对应单列索引 status + create_time组合查询:
MySQL 只会选择其中一个最优单列索引,不会同时用两个,剩余条件走全表过滤;
极少数场景会触发 索引合并,但效率远低于联合索引。
2. 联合索引(多字段一起建)优点
- 组合查询效率极高
业务高频status + create_time筛选、排序、分页时,联合索引一次定位,无需回表/二次过滤,性能远优于两个单列索引。 - 支持索引排序
WHERE status=? ORDER BY create_time:联合索引天然有序,避免文件排序(Using filesort)。 - 索引体积更小、维护开销低
一条联合索引 ≈ 一条索引结构;
两条单列索引是两份独立索引,占用更多磁盘、内存。 - 可实现覆盖索引
若查询字段只有status、create_time,直接从索引取数,无需回表。
3. 联合索引 缺点
- 受最左前缀限制
单独查后序字段(仅create_time)无法使用该索引,灵活性差。 - 字段顺序影响极大
顺序写反(create_time, status),查询逻辑也会跟着受限。 - 字段过多会导致索引臃肿
联合索引不建议超过3个字段,索引树变大、查询变慢。 - 写入开销略增(相对单字段)
新增/更新status或create_time,都要维护这条联合索引;字段越多,写入压力越大。
4. 两个单列索引 优点
- 查询灵活性强
两个字段单独查询都能走索引,互不影响。 - 单字段更新时,索引维护压力分散。
5. 两个单列索引 缺点
- 组合查询性能弱
多条件同时筛选时,大多只选一个索引,另一个条件全表过滤;索引合并效率很低。 - 占用存储空间更大
两份独立索引,缓冲池占用更高。 - 无法优化联合排序
WHERE status=? ORDER BY create_time极易出现Using filesort。 - 索引数量多,插入/更新/删除整体开销更大(每条DML都要维护多个索引)。
三、业务场景该怎么选?
场景1:业务主流是 status + create_time 组合查询、筛选、排序
✅ 优先用 联合索引 (status, create_time)
例:查询「某状态下、按创建时间排序」的订单。
场景2:两个字段经常单独查询,很少组合使用
✅ 优先用 两个单列索引
例:有时只查状态、有时只按时间查,组合查询极少。
场景3:既要单独查,又要高频组合查
方案:
- 保留联合索引
(status, create_time)(覆盖单status + 组合查询) - 额外单独给
create_time建一个单列索引(满足仅查时间的场景)
四、补充总结(精简版)
- 联合索引:适合多字段组合查询/排序,性能强、体积小;缺点是受最左前缀约束,单独查后置字段失效。
- 多个单列索引:适合字段独立查询,灵活度高;组合查询性能差、索引冗余、写入开销大。
- 索引不是越多越好,线上优先按业务查询模式选型,严控索引数量。