第4章:索引设计原则——单列索引、复合索引、覆盖索引
索引这东西,在Room开发里就像一把双刃剑。用好了,查询快如闪电;用不好,写入慢得想哭。我记得刚入行那会儿,遇到一个列表加载特别慢的页面,我二话不说给每个字段都加了索引,结果写入操作直接崩了。后来才明白,索引不是越多越好,关键得知道怎么设计。
这一章我们就来聊聊三种最常用的索引类型:单列索引、复合索引、覆盖索引。我不会和你讲太多理论,而是结合我踩过的坑,说点能直接用上的东西。
单列索引:最基础的加速器
单列索引,说白了就是给一个字段建索引。比如你有一张用户表,经常根据 userId 来查,那就在这个字段上加索引。
CREATE INDEX idx_user_id ON User (userId);
在 Room 中,你需要在实体类上用 @Index 注解:
@Entity(indices = {@Index(value = "userId")})
public class User {
@PrimaryKey
public long id;
public String userId;
public String name;
}
这种索引适用于单一条件查询,比如:
SELECT * FROM User WHERE userId = ?;
速度提升很明显,但要注意——如果这个字段的区分度很低(比如性别字段只有男/女),索引的作用就很有限。我在一个项目中遇到过,给一个状态字段加了索引,结果扫描了几乎整张表,还不如全表扫描快。嗯,这里要绕开这个坑。
复合索引:多条件查询的利器
复合索引就是把多个字段绑在一起建索引。比如我们经常按 departmentId + createTime 来查记录:
CREATE INDEX idx_dep_time ON Employee (departmentId, createTime);
在 Room 中这样写:
@Entity(indices = {@Index(value = {"departmentId", "createTime"})})
public class Employee { ... }
使用复合索引有个核心原则叫“最左前缀”。什么意思?就是查询条件必须从索引的最左边开始匹配。比如 departmentId 是左边的字段,如果你只查 createTime 而忽略 departmentId,这个复合索引就派不上用场。
我之前优化过一个报表查询,原本有300万条数据,查询要5秒多。我把 region 和 date 建成复合索引,并且把 region 放左边(因为等值查询),查询时间直接降到80ms。你看,顺序不同,性能差距能差几十倍。
覆盖索引:让查询不走“回头路”
覆盖索引的概念稍微进阶一点。它是指:索引本身已经包含了查询所需的所有字段,数据库不需要再去表格里取数据(业务上叫“回表”)。
举个例子,假如我们经常做这个查询:
SELECT id, name, email FROM User WHERE email = ?;
如果只在 email 上建单列索引,查询时会先找到符合条件的行ID,然后再根据ID去主表中找 name 和 id。这就多了一次回表操作。
如果我们建一个复合索引 (email, id, name),那么索引本身就能满足查询,数据库直接返回结果,完全不用碰主表。这就是覆盖索引。
CREATE INDEX idx_cover ON User (email, id, name);
三种索引的对比
| 索引类型 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 单列索引 | 单一字段频繁等值或范围查询 | 简单、开销小 | 多条件组合查询时效率低 |
| 复合索引 | 多字段组合查询(等值+排序/范围) | 按最左前缀兼容多种查询 | 字段顺序要求严格,写放大 |
| 覆盖索引 | 查询字段全部在索引内 | 避免回表,IO极低 | 索引体积大,更新开销高 |
避坑指南
我曾经接手过一个已上线的项目,业务方反馈某列表页滑动卡顿。我一看,表里有7个单列索引,但查询却用了三个条件的组合查询。7个索引没有一个能直接命中。我重建了一个复合索引,字段顺序按 status、type、createTime 排列(因为status和type都是等值查询,createTime用于排序),修改后效果立竿见影。
所以我的建议是:
- 先分析常用查询条件,再决定用单列还是复合。
- 复合索引的字段顺序:等值查询放最左,然后范围查询或排序字段。
- 能用覆盖索引时尽量用,但要控制字段数量,别超过5个。
好了,三种索引设计原则讲完了。你可以在自己的项目里试试,做一个查询分析,看看哪些查询需要改进。