hanyueqi 2016-12-06
需求
最近收到一个业务需求,需求是基于电影票售卖的不同渠道价格存储。某一个场次的电影,不同的销售渠道对应不同的价格。整理需求为:
数据字段:
场次信息;
播放影片信息;
渠道信息,与其对应的价格;
渠道数量最多几十个;
业务查询有两种:
根据电影场次,查询某一个渠道的价格;
根据渠道信息,查询对应的所有场次信息;
建模
不好的
我们先来看其中一种典型的**不好**建模设计:
{ "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建模设计,开发人员在进行建模设计时经常也会受传统数据库的思路影响,沿用之前的思维惯性,而忽略了“文档”的价值。