哈希索引仅支持$eq和单值$in查询,不支持$gt、$in(多值)、$regex等范围或模式匹配操作;其本质是将字段值哈希为固定长度整数建索引,导致原始值的大小、前缀、范围信息丢失,故无法用于范围查询、正则匹配、数组字段、复合索引含数组字段、浮点数(存在截断冲突)、嵌入式文档覆盖查询及唯一性约束,且仅在分片集群中用于解决数据倾斜问题,单机使用反而低效。

哈希索引只支持 equals 查询,不支持 $gt、$in(含数组)、$regex
哈希索引本质是把字段值哈希后存为固定长度的整数,再按该整数排序建索引。这意味着原始值的大小关系、前缀、范围等信息全部丢失。所以你用 { age: { $gt: 25 } } 或 { name: { $regex: "^A" } } 时,MongoDB 直接忽略哈希索引,退回到全表扫描。
常见错误现象:明明建了 db.users.createIndex({ email: "hashed" }),但 db.users.find({ email: { $in: ["a@b.com", "c@d.com"] } }) 还是慢——因为 $in 在哈希索引上不生效(除非是单个精确值)。
-
$eq和单值$in(如{ email: { $in: ["x@y.com"] } })能走哈希索引 -
$ne、$lt、$gte、$exists等一律不支持 - 对浮点数建哈希索引要特别小心:
2.1、2.9会被截断为2,哈希后完全相同 → 冲突,查不准
哈希索引不能和数组字段共存,也不能用于复合索引中的任意字段
只要字段值是数组,或者字段所在文档的该字段是数组(哪怕只有一条文档如此),MongoDB 就拒绝创建哈希索引。这不是配置问题,是硬性限制。
更隐蔽的是复合索引场景:哪怕你只打算对 user_id 建哈希,但只要 tags 字段是数组,db.coll.createIndex({ user_id: "hashed", tags: 1 }) 就会失败 —— MongoDB 把整个索引判定为“可能触发多键”,直接拦掉。
- 检查字段是否含数组:
db.coll.findOne({ "field.0": { $exists: true } }) - 想用哈希 + 其他字段?只能拆成两个独立索引:
{ user_id: "hashed" }和{ user_id: 1, status: 1 } - 嵌入式文档可以哈希,但整个子文档被折叠哈希(比如
{ addr: { city: "Beijing", zip: "100000" } }被当成一个 blob 计算哈希)
哈希索引无法覆盖查询(indexOnly),且不支持 unique
即使你的查询只投影哈希字段本身,比如 db.users.find({ _id: ObjectId("...") }, { email: 1, _id: 0 }),MongoDB 仍需回表读取原始文档 —— 因为哈希值不是原始值,它没法直接返回 email 字符串。
同时,createIndex({ email: "hashed" }, { unique: true }) 会报错:cannot create unique index over hashed field。唯一性必须靠额外建一个普通索引实现,比如再补一个 { email: 1 } 并设 unique: true。
- 哈希索引永远不参与
explain().queryPlanner.indexesUsed的覆盖判断 - 分片集群中,哈希索引常用于分片键(如
{ user_id: "hashed" }),但它本身不提供业务层唯一性保障 - 如果你依赖
email唯一,别指望哈希索引;它只加速find({ email: "x@y.com" })这类查找
哈希索引在分片集群里才有实际价值,单机几乎没优势
单机 MongoDB 上,哈希索引比普通 B-tree 索引更慢、更占空间、功能更少。它的设计目标从来不是“加速单机查询”,而是解决分片时数据倾斜问题。
比如用 _id(ObjectId)做范围分片,新写入全打到同一个分片;换成哈希分片后,ObjectId 被哈希打散,写入均匀分布到各分片。但代价是:所有范围查询变成广播查询(发给所有分片)。
- 单机环境建哈希索引,通常只是照搬分片配置,反而引入不必要的限制
- 真正需要哈希索引的信号只有一个:你在用
sh.shardCollection(),且分片键字段单调递增(如时间戳、自增ID) - 验证是否真走哈希:用
db.runCommand({ convertShardKeyToHashed: { _id: ObjectId("...") } })看哈希结果,再对比explain()中的executionStats

















