mongodb gridfs在分片集群中必须用_id: "hashed"分片,且仅此一种合法方式;fs.files与fs.chunks强制共用该策略,其他分片键均报错或导致性能崩溃。

MongoDB GridFS 在分片集群中不能按业务字段手动分片,必须用 _id 哈希分片,否则会报错或性能崩溃。这是硬性限制,不是配置疏漏。
sh.shardCollection("db.fs.files", {"_id": "hashed"}) 是唯一合法方式
GridFS 的 fs.files 和 fs.chunks 属于命名空间绑定对,MongoDB 6.0+ 强制要求两者共用同一个分片键:_id,且只能是 "hashed" 策略。
常见错误操作包括:
- 对
fs.chunks单独执行sh.shardCollection("db.fs.chunks", {"files_id": "hashed"})→ 直接报错:cannot shard collection with non-_id shard key on a GridFS namespace - 尝试用
{filename: 1}或{user_id: 1}分片fs.files→ 写入失败或数据全挤在单个分片 - 先写入大量文件,再执行分片 →
shardCollection对已有数据无效,必须清空重建
正确流程只有一步:sh.shardCollection("mydb.fs.files", {"_id": "hashed"})。执行后,fs.chunks 自动继承该策略,无需额外命令。
为什么按 filename 查询会变慢?索引比分片键更重要
因为分片键锁死为 _id,所有非 _id 查询(如 db.fs.files.find({filename: "report.pdf"}))都会触发广播查询——mongos 向所有分片发请求,再合并结果。
解决方法不是改分片键(做不到),而是补二级索引:
-
db.fs.files.createIndex({"filename": 1})—— 单字段查文件名 -
db.fs.files.createIndex({"metadata.user_id": 1, "uploadDate": -1})—— 复合查询带时间排序 - 避免在低基数字段上建索引,比如
{"metadata.status": 1}(只有 "pending"/"done" 两种值)
没有这些索引,即使分片均匀,find({filename: ...}) 也会越来越慢,且慢日志里频繁出现 scatter-gather 模式。
chunkSize 设置不当会让分片失效
默认 chunkSizeBytes: 256 * 1024 在分片环境下容易引发倾斜:小文件生成大量 chunk,而它们的 files_id(即 fs.files._id)哈希后仍可能扎堆。
调整建议取决于你的写入模式:
- 若文件天然按用户隔离(如每个
user_id上传几十个文件),可保持默认或设为512 * 1024,重点保证_id散列度(别用时间戳字符串当_id) - 若文件类型混杂、无强业务键,建议设为
1024 * 1024(1MB),减少fs.chunks文档总数,缓解元数据压力 - 绝对不要设 >
8 * 1024 * 1024—— WiredTiger 单文档上限 16MB,chunk 还要存files_id、n、data字段,超限直接报错:Document too large
验证是否真分片成功,别只看 sh.status()
sh.status() 显示 fs.files 已分片,不代表数据分布合理或查询高效。必须做三件事:
- 查
db.fs.files.getShardDistribution(),确认 chunk 数量在各分片间偏差 - 用
db.fs.files.find({filename: "test.jpg"}).explain("executionStats"),看executionStages.shards是否列出全部分片,且nReturned不是 0 - 查
db.fs.chunks.find({files_id: ObjectId("...")}).explain("executionStats"),确认nShards === 实际分片数,否则说明路由失败,chunk 被误路由到错误分片
真正难的是让 _id 哈希后足够散列,又不破坏业务语义——如果业务强依赖 filename 查询,那索引和驱动层缓存比折腾分片键更实在。











