(status, create_time)联合索引通常低效,因status基数低导致索引分支下数据无序、优化器预估不准、范围查询使create_time失效、写入开销大;应优先用高选择性字段前置或拆分为单列索引+覆盖索引。

状态字段(如 status)和时间字段(如 create_time)一起建联合索引,多数情况下是低效甚至反效果的——除非你明确知道查询模式且已验证执行计划。
为什么 (status, create_time) 索引常被误用
状态字段通常基数极低(比如只有 'active'/'inactive'/'pending'),而时间字段区分度高但常用于范围查询。B+ 树第一层按 status 分割后,每个分支下 create_time 的数据量巨大且无序(因 status 值重复太多),导致:
- 优化器预估行数严重不准,容易跳过该索引,回退为全表扫描
- 即使走了索引,
key_len往往只显示用了第一列(status),create_time因后续是范围条件或未参与等值过滤而失效 - 写入时索引维护开销大,尤其在高频更新
status的场景(如订单状态流转)
WHERE status = ? AND create_time > ? 该怎么建索引
这个查询看似适合 (status, create_time),但实际执行中,create_time > ? 是范围条件,它会让联合索引从该列起截断——也就是说,只有 status 和 create_time 能用上,没有第三列可“继续匹配”。更关键的是:如果 status 值分布不均(比如 95% 是 'active'),MySQL 很可能认为走这个索引比全表扫描还慢。
支持从 AWS、阿里云、GCP、华为云、Azure 向腾讯云迁移的 Landing Zone 全流程客户端。通过远程 MCP Server 自动完成:扫描源云资源(5朵云13种资源)、AI智能填充调研问卷、生成设计文档、生成Terraform代码。当用户提到云迁移、Landing Zone、Terraform、扫...
- 优先考虑把高选择性字段前置:若业务中常按
user_id或tenant_id过滤,应改为(user_id, status, create_time) - 若确实只有这两个字段参与 WHERE,且
status值较均衡(如 5~10 个状态、各占 10% 左右),可保留(status, create_time),但必须配合ANALYZE TABLE更新统计信息 - 避免
ORDER BY create_time DESC单独依赖该索引:因为status是等值,create_time是范围,排序仍会触发Using filesort
替代方案:覆盖索引 + 单独时间索引更可靠
与其强行塞进一个低效联合索引,不如拆解职责:
- 为高频等值过滤建
status索引(单列即可),尤其当status配合LIMIT或小结果集查询时有效 - 为时间范围查询单独建
create_time索引,特别是用于分页、定时任务拉取、后台报表等场景 - 若查询只查少数几列(如
id,status,create_time),直接建覆盖索引:CREATE INDEX idx_status_ct_cover ON t (status, create_time, id)——这样无需回表,status = ? AND create_time > ?可完整走索引 - 注意:覆盖索引不能解决
SELECT *场景,此时仍需回表,性能提升有限
EXPLAIN 中一眼识别是否真用了联合索引
别只看 key 是否非空,重点检查三处:
-
type:必须是ref或range,不是ALL或index -
key_len:对(status, create_time)索引,status = ?时应为 2(假设 TINYINT),status = ? AND create_time > ?时也应为 2——如果仍是 2,说明create_time没参与索引查找,只是被用来过滤 -
Extra:出现Using index condition表示用了 ICP(索引条件下推),是好信号;若出现Using where; Using filesort,说明排序没借上力,得另加索引或改写 SQL
最易被忽略的一点:状态字段更新频繁时,(status, create_time) 索引的 B+ 树节点分裂比想象中更频繁,尤其在批量更新场景下,可能引发明显锁竞争和 buffer pool 冲刷——这时宁可多建一个单列索引,也不强求“看起来更整齐”的联合索引。

















