索引存在但未被使用是最危险状态,必须用explain()验证;字段类型变更(如字符串改数字)、非前缀正则(如/lice/)、复合索引中范围查询后字段、不满足最左前缀、分片下唯一索引不含分片键前缀,均导致索引失效。

索引存在,但查询没走它——这是最危险的状态。别信“建了就生效”,explain() 才是唯一判据。
字段类型变更后索引直接失效
用 $set 把字符串改成数字(比如 {"status": "1"} → {"status": 1}),旧索引条目仍按原始 BSON 类型编码,新值无法匹配,查询退化为 COLLSCAN。
- 改类型前必须先运行
db.collection.getIndexes(),确认该字段是否被索引覆盖 - 已有索引时,不能跳过
dropIndex()直接更新数据;否则重建索引会失败或仍不生效 - 复合索引更敏感:哪怕只改了
{status: 1, createdAt: -1}里的status类型,整个索引都失效
正则表达式写法导致索引完全不参与
只有前缀匹配能利用索引,比如 /^Alice/;而 /lice/、/son$/ 或 .*alice 这类非前缀正则,MongoDB 无法利用索引有序性,等效于全表扫描。
- 检查方式:用
db.collection.explain("executionStats").find({name: /lice/})看executionStages.stage是不是COLLSCAN - 替代方案:对模糊搜索字段单独建文本索引(
createIndex({name: "text"})),但注意它不支持精确匹配优化 - 避免在高频查询字段上依赖通配正则——这不是索引能解决的问题,而是查询设计缺陷
复合索引中范围查询后字段全部失效
在索引 {a: 1, b: 1, c: 1} 上,查询 {a: 1, b: {$gt: 2}, c: 3} 时,c 字段不会走索引——因为 b 是范围条件,c 已超出索引“有效前缀”范围。
-
$gt、$lt、$in、$ne、$not都属于范围语义,后续字段全部失效 - 若业务需同时查
b和c,考虑调整索引顺序,如{a: 1, c: 1, b: 1},把等值字段放范围字段前 - 最左前缀不满足(比如只查
{c: 3})时,整个索引也不会被使用
分片集群下唯一索引形同虚设
分片环境下,MongoDB 不支持跨分片的单字段唯一索引。如果唯一索引不含完整分片键前缀,插入时不会校验全局唯一性,可能在不同分片上写入相同值。
- 正确做法:唯一索引必须以完整分片键开头,例如分片键是
{orgId: 1, userId: 1},那唯一索引应为{orgId: 1, userId: 1, email: 1} - 不要依赖
db.collection.createIndex({email: 1}, {unique: true})来保证分片集合的全局唯一——它只在单个分片内生效 - 验证一致性:运行
db.runCommand({checkMetadataConsistency: 1, checkIndexes: true})
最容易被忽略的是存量数据与新索引类型的割裂——schema validation 没开,类型混存却以为索引还能兜底。











