需求
最近收到一个业务需求,需求是基于电影票售卖的不同渠道价格存储。某一个场次的电影,不同的销售渠道对应不同的价格。整理需求为:
-
数据字段:
- 场次信息;
- 播放影片信息;
- 渠道信息,与其对应的价格;
- 渠道数量最多几十个;
-
业务查询有两种:
- 根据电影场次,查询某一个渠道的价格;
- 根据渠道信息,查询对应的所有场次信息;
建模
不好的
我们先来看其中一种典型的不好建模设计:
{
"scheduleId": "0001",
"movie": "你的名字",
"price": {
"gewala": 30,
"maoyan": 50,
"taopiao": 20
}
}
数据表达上基本没有字段冗余,非常紧凑。再来看业务查询能力:
-
根据电影场次,查询某一个渠道的价格;
- 建立
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
}
与上面的方案相比,把整个存储对象结构进行了平铺展开,变成了一种表结构,传统的关系数据库多数采用这种类型的方案。信息表达上,把一个对象按照渠道维度拆成多个,其他的字段进行了冗余存储。如果业务需求再复杂点,造成的信息冗余膨胀非常巨大。膨胀后带来的副作用会有磁盘空间占用上升,内存命中率降低等缺点。对查询的处理呢:
-
根据电影场次,查询某一个渠道的价格;
- 建立
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
索引,详细信息可以参考官方文档。
]说明
-
根据电影场次,查询某一个渠道的价格;
- 建立
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建模设计,开发人员在进行建模设计时经常也会受传统数据库的思路影响,沿用之前的思维惯性,而忽略了“文档”的价值。