嵌套对象适合语言固定、字段饱满的读多写少场景,如商品标题;属性模式才是动态增删语言和稀疏翻译的扩展性解法,需配合索引、arrayfilters与collation等机制。

直接用嵌套对象(name.en、name.zh)最简单,但只适合语言种类固定、新增极少、且字段值几乎不为空的场景;一旦要动态增删语言或支持稀疏翻译,就得切到属性模式(title 数组 + lang/value 结构),否则索引失效、查询变慢、Schema 被锁死。
什么时候该用嵌套对象?
适合读多写少、语言集明确、且每条文档大概率填满所有语言字段的场景,比如商品标题、CMS 页面标题等。它一次查询全量返回,无额外聚合开销。
- 字段结构清晰:
{"name": {"en": "Apple", "zh": "苹果", "ja": "リンゴ"}} - 查单个语言快:
db.products.find({ "name.en": "Apple" })可走普通索引 - 新增语言只需
$set,不用改 Schema:db.products.updateOne({ _id: 1 }, { $set: { "name.de": "Apfel" } }) - 但别滥用:如果 80% 文档只有
en字段,其余全为null,BSON 存储膨胀 + 压缩率下降会明显拖慢全表扫描
为什么属性模式(Attribute Pattern)才是扩展性解法?
当你需要支持任意语言动态上线、或翻译覆盖率严重不均时,属性模式是唯一能兼顾查询性能和 Schema 灵活性的设计。它把语言作为数组元素的元数据,而不是字段名。
- 文档结构示例:
{"title": [{"lang": "en", "value": "Intro"}, {"lang": "zh-CN", "value": "入门"}]} - 索引只需一个:
db.courses.createIndex({"title.lang": 1, "title.value": 1}),查中文标题就用find({ "title.lang": "zh-CN", "title.value": "入门" }),完全命中索引 - 更新必须用
arrayFilters:db.courses.updateOne({ _id: 1 }, { $set: { "title.$[elem].value": "新标题" } }, { arrayFilters: [{ "elem.lang": "zh-CN" }] }),不能硬写title.0.value——下标不稳定 - 聚合时小心
$unwind:对title字段$unwind会让一条文档变成 N 条,记得配$group或用$first控制输出
GridFS 文件怎么带语言标识?
GridFS 的 metadata 是纯用户自定义 JSON,MongoDB 不解析也不索引其中的 lang 字段。你写了 {"lang": "fr"} 没用,除非你自己约定、校验、建索引。
- 上传时强制统一键名:
lang(小写),值必须匹配正则/^[a-z]{2}(-[A-Z]{2})?$/,如"en"、"pt-BR" - 必须手动建索引:
db.fs.files.createIndex({ "metadata.lang": 1 }),否则find({ "metadata.lang": "ja" })是全表扫描 -
metadata不可原子更新,上传时就要写全;没写lang的文件仍会进入索引(值为null),如需严格隔离,上传前就得校验非空 - 别指望
metadata.lang触发全文搜索——$text索引只作用于字符串字段内容本身,GridFS 存的是二进制块。真要做多语言文本检索,得另建摘要集合,存{ fileId: ..., lang: "es", excerpt: "..." }并建复合文本索引
别漏掉 collation 和 text index 的语言适配
即使字段结构没问题,字符串比较和全文检索仍可能因语言规则错乱而返回错误结果。MongoDB 默认按英文规则处理大小写、重音、词干,非英语查询必须显式指定。
- 排序/去重时加
collation:db.words.find().sort({ word: 1 }).collation({ locale: "zh" }),否则é和e可能被当成不同字符 - 建
$text索引时设default_language:db.quotes.createIndex({ quote: "text" }, { default_language: "spanish" }),否则西班牙语停用词(如hay)不会被过滤,词干提取也错位 - 注意:MongoDB 官方已建议优先用
$search配合 MongoDB Search,$text属于旧机制,功能受限且维护减弱
真正难的不是选哪种结构,而是判断哪些字段值得用属性模式——title 和 description 必须支持任意语言增删,而 slug 或 seo_title 通常只维护一两种语言,就该单独建字段。设计时先问一句:这个字段未来一年会不会新增 3 种以上语言?如果答案是“很可能”,那就别省事,直接上属性模式。











