在MongoDB中使用动态密钥字段的JSON模式

时间:2013-07-26 09:23:44

标签: mongodb jsonschema

希望对存储在mongodb集合中的对象提供i18n支持

目前我们的架构如下:

{
  _id: "id"
  name: "name"
  localization: [{
    lan: "en-US",
    name: "name_in_english"
  }, {
    lan: "zh-TW",
    name: "name_in_traditional_chinese"
  }]
}

但我的想法是字段“lan”是唯一的,我可以只使用此字段作为键,因此结构将是

{
  _id: "id"
  name: "name"
  localization: {
    "en-US": "name_in_english",
    "zh-TW": "name_in_traditional_chinese"
  }
}

这将更整洁,更容易解析(只是本地化[语言]将获得我想要的特定语言的价值)。

但问题是:这是在MongoDB中存储数据的一个好方法吗?以及如何通过json-schema检查?

3 个答案:

答案 0 :(得分:7)

将值作为键是不好的做法。语言代码是值,正如您所说,您无法根据模式验证它们。它使查询不可能。例如,您无法弄清楚是否为“nl-NL”进行语言翻译,因为您无法与键进行比较,也无法轻松将其编入索引。您应始终具有描述性密钥。

然而,正如您所说,将语言作为键使得将数据拉出更容易,因为您可以通过['nl-NL'](或者您的语言的任何语法)访问它。

我会建议另一种模式:

{
    your_id: "id_for_name"
    lan: "en-US",
    name: "name_in_english"
}
{
    your_id: "id_for_name"
    lan: "zh-TW",
    name: "name_in_traditional_chinese"
}

现在你可以:

  • { your_id: 1, lan: 1 }设置索引,以便快速查找
  • 单独查询每个翻译并获得该翻译:
    db.so.find( { your_id: "id_for_name", lan: 'en-US' } )
  • 使用相同的索引查询每个id的所有版本:
    db.so.find( { your_id: "id_for_name" } )
  • 还可以更轻松地更新特定语言的翻译:

    db.so.update(
        { your_id: "id_for_name", lan: 'en-US' }, 
        { $set: { name: "ooga" } } 
    )
    

使用您建议的架构时,这些点都不可能。

答案 1 :(得分:1)

显然第二个模式示例对你的任务来说要好得多(当然,如果你提到的lan字段是唯一的,那对我来说也是如此)。

dictionary/associated array/mapping/whatever_it_is_called_in_your_language获取元素比扫描整个数组值便宜得多(在当前情况下,从存储大小的角度来看它也非常有效(请记住,所有字段都存储在MongoDB as-is中) ,所以每个记录都包含json字段的整个键名,而不是它的表示或索引或其他任何内容。

我的经验表明,MongoDB已经足够成熟,可以用作应用程序的主存储器,即使是在高负载(无论它意味着什么;)),主要问题是你如何对抗数据库级锁(嗯,我们将等待承诺的表级锁,它会强制MongoDB我希望更多),尽管如果你的MongoDB集群构建得很糟糕,数据丢失是可能的(通过Internet挖掘文档和文章以获取更多信息)。

至于模式检查,你必须在插入记录之前通过应用程序端的编程语言来实现,是的,这就是为什么称Mongo为schemaless

答案 2 :(得分:0)

有一种情况是对象必然比数组更好:支持将upserts放入集合中。例如,如果要更新具有name'item1'的项目以具有val 100,或者如果一个项目不存在则插入此类项目,则只需一次原子操作。使用数组,您必须执行两个操作之一。给出类似

的架构
{ _id: 'some-id', itemSet: [ { name: 'an-item', val: 123 } ] }

你有命令

// Update:
db.coll.update(
  { _id: id, 'itemSet.name': 'item1' },
  { $set: { 'itemSet.$.val': 100 } }
);

// Insert:
db.coll.update(
  { _id: id, 'itemSet.name': { $ne: 'item1' } },
  { $addToSet: { 'itemSet': { name: 'item1', val: 100 } } }
);

您必须首先询问是否需要提前知道哪些内容,这可能会加剧竞争条件,除非您实施某些版本控制。使用对象,您只需执行

db.coll.update({
  { _id: id },
  { $set: { 'itemSet.name': 'item1', 'itemSet.val': 100 } }
});

如果这是一个用例,那么你应该采用对象方法。一个缺点是查询特定名称需要扫描。如果还需要,您可以添加一个专门用于索引的单独数组。这是与MongoDB的权衡。 Upserts将成为

db.coll.update({
  { _id: id },
  { 
    $set: { 'itemSet.name': 'item1', 'itemSet.val': 100 },
    $addToSet: { itemNames: 'item1' } 
  }
});

然后查询将只是

db.coll.find({ itemNames: 'item1' })

(注意:$位置运算符不支持数组upsert。)