布尔字段status单独建索引基本无效,因选择性低导致优化器倾向全表扫描;有效策略是与高区分度字段组合成联合索引,如(user_id, status)、(created_at, status)或(status, id)覆盖查询。

区分度极低的布尔字段(如 status 只有 0/1 两个值)通常不适合单独建索引——但“不适合单独建”不等于“完全不能用索引”,关键在于怎么用、和谁组合、在什么场景下生效。
为什么 status 单独建索引基本无效
MySQL 优化器会估算索引的选择性,当 COUNT(DISTINCT status) / COUNT(*) 接近 0.5(比如 49% 是 0、51% 是 1),优化器大概率直接跳过该索引,走全表扫描。因为回表或遍历索引页的成本,可能比顺序读几页数据还高。EXPLAIN 中你会看到 type=ALL 或 key=NULL,即使索引存在。
真正有效的三种组合策略
低区分度字段必须“搭便车”,绑定高区分度字段形成联合索引,才能被实际命中:
- 高频等值查询 + 状态过滤:比如查“某用户所有已支付订单”,
user_id区分度接近 1,status是固定值,建(user_id, status)——WHERE user_id = ? AND status = 1完全命中 - 范围查询 + 状态排序:比如查“最近 7 天创建的待审核订单”,
created_at是范围条件,status是等值过滤,建(created_at, status)(注意:范围列必须放前面,status放后才有效) - 覆盖查询避免回表:如果只查
id和status,而id是主键,可建INDEX idx_status_cover (status, id),让SELECT id FROM orders WHERE status = 1直接从索引取值(Extra 显示Using index)
容易被忽略的坑:顺序、隐式转换与写入代价
联合索引里低区分度字段的位置很关键:
- 别把
status放最左:除非你真要查“所有已取消订单”,否则WHERE status = 0几乎不会走索引 - 别在
status上做运算:WHERE status + 0 = 1或WHERE CAST(status AS CHAR) = '1'会让索引失效 - 别忽略写入放大:每新增一条记录,都要更新所有含
status的联合索引。如果表日均写入 10 万行,又建了 3 个带status的索引,那每天多出 30 万次索引页写入
真正难的不是“能不能建”,而是判断这个 status 字段在哪些查询路径中是“确定性过滤条件”,且前面有足够强的筛选能力——没这个上下文,光看字段本身,建了也白建。











