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

hanyueqi 2016-12-06

需求

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

  • 数据字段:

  1. 场次信息;

  2. 播放影片信息;

  3. 渠道信息,与其对应的价格;

  4. 渠道数量最多几十个;

业务查询有两种:

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

  2. 根据渠道信息,查询对应的所有场次信息;

建模

不好的

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

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

}

}

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

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

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

根据渠道信息,查询对应的所有场次信息;

  • 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})索引;

根据渠道信息,查询对应的所有场次信息;

  • 建立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})索引;

根据渠道信息,查询对应的所有场次信息;

  • 建立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建模设计,开发人员在进行建模设计时经常也会受传统数据库的思路影响,沿用之前的思维惯性,而忽略了“文档”的价值。

相关推荐