MongoDB数据建模小案例:多列数据结构

需求

最近收到一个业务需求,需求是基于电影票售卖的不同渠道价格存储。某一个场次的电影,不同的销售渠道对应不同的价格。整理需求为:

  • 数据字段:

    1. 场次信息;
    2. 播放影片信息;
    3. 渠道信息,与其对应的价格;
    4. 渠道数量最多几十个;
  • 业务查询有两种:

    1. 根据电影场次,查询某一个渠道的价格;
    2. 根据渠道信息,查询对应的所有场次信息;

建模

不好的

我们先来看其中一种典型的不好建模设计:

{
  "scheduleId": "0001",
  "movie": "你的名字",
  "price": {
    "gewala": 30,
    "maoyan": 50,
    "taopiao": 20
  }
}

数据表达上基本没有字段冗余,非常紧凑。再来看业务查询能力:

  1. 根据电影场次,查询某一个渠道的价格;

    • 建立createIndex({scheduleId:1, movie:1})索引,虽然对price来说没有创建索引优化,但通过前面两个维度,已经可以定位到唯一的文档,查询效率上来说尚可;
  2. 根据渠道信息,查询对应的所有场次信息;

    • 为了优化这种查询,需要对每个渠道分别建立索引,例如:

      • createIndex({"price.gewala":1})
      • createIndex({"price.maoyan":1})
      • createIndex({"price.taopiao":1})
    • 但渠道会经常变化,并且为了支持此类查询,肯能需要创建几十个索引,对维护来说简直就是噩梦;

此设计行不通,否决。

一般般的设计

{
  "scheduleId": "0001",
  "movie": "你的名字",
  "channel": "gewala",
  "price": 30
}

{
  "scheduleId": "0001",
  "movie": "你的名字",
  "channel": "maoyan",
  "price": 50
}

{
  "scheduleId": "0001",
  "movie": "你的名字",
  "channel": "taopiao",
  "price": 20
}

与上面的方案相比,把整个存储对象结构进行了平铺展开,变成了一种表结构,传统的关系数据库多数采用这种类型的方案。信息表达上,把一个对象按照渠道维度拆成多个,其他的字段进行了冗余存储。如果业务需求再复杂点,造成的信息冗余膨胀非常巨大。膨胀后带来的副作用会有磁盘空间占用上升,内存命中率降低等缺点。对查询的处理呢:

  1. 根据电影场次,查询某一个渠道的价格;

    • 建立createIndex({scheduleId:1, movie:1, channel:1})索引;
  2. 根据渠道信息,查询对应的所有场次信息;

    • 建立createIndex({channel:1})索引;

更进一步的优化呢?

合理的设计

{
  "scheduleId": "0001",
  "movie": "你的名字",
  "provider": [
    {
      "channel": "gewala",
      "price": 30
    },
    {
      "channel": "maoyan",
      "price": 50
    },
    {
      "channel": "taopiao",
      "price": 20
    }
  ]
}

注意看,这里使用了在MongoDB建模中非常容易忽略的结构--”数组“。查询方面的处理,是可以建立Multikey Index索引,详细信息可以参考官方文档
]说明

  1. 根据电影场次,查询某一个渠道的价格;

    • 建立createIndex({scheduleId:1, movie:1, "provider.channel":1})索引;
  2. 根据渠道信息,查询对应的所有场次信息;

    • 建立createIndex({"provider.channel":1})索引;

    再通过explain来验证上面两个索引是否起到作用:

db.movie.find({"scheduleId":"0001","movie":"你的名字", "provider.channel":"taopiao"}).explain()

......
  "winningPlan": {
    "stage": "FETCH",
    "inputStage": {
      "stage": "IXSCAN",
      "keyPattern": {
        "scheduleId": 1,
        "movie": 1,
        "provider.channel": 1
      },
      "indexName": "scheduleId_1_movie_1_provider.channel_1",
      "isMultiKey": true,
......
db.movie.find({"provider.channel":"taopiao"}).explain()

......
  "winningPlan": {
    "stage": "FETCH",
    "inputStage": {
      "stage": "IXSCAN",
      "keyPattern": {
        "provider.channel": 1
      },
      "indexName": "provider.channel_1",
      "isMultiKey": true,
......

总结

这个案例并不复杂,需求也很清晰,但确实非常典型的MongoDB建模设计,开发人员在进行建模设计时经常也会受传统数据库的思路影响,沿用之前的思维惯性,而忽略了“文档”的价值。

上一篇:Ubuntu小技巧


下一篇:ECS DAY3LSB负载均衡实践