关系型数据库解决方案
上述问题使用传统的关系型数据库也可以解决,比如以下几种方案
针对不同商品,创建不同的表
比如音乐专辑、电影这2种商品,有一部分共同的属性,但也有很多自身特有的属性,可以创建2个不同的表,拥有不同的schema。
create table `product_audio_album` ( `sku` char(8) not null, ... `artist` varchar(255) default null, `genre_0` varchar(255) default null, `genre_1` varchar(255) default null, ..., primary key(`sku`)) ... create table `product_film` ( `sku` char(8) not null, ... `title` varchar(255) default null, `rating` char(8) default null, ..., primary key(`sku`)) ...
这种做法的主要问题在于
针对每个新的商品分类,都需要创建新的表
应用程序开发者必须显式的将请求分发到对应的表上来查询,一次查询多种商品实现起来比较麻烦
所有商品存储到单张表
create table `product` ( `sku` char(8) not null, ... `artist` varchar(255) default null, `genre_0` varchar(255) default null, `genre_1` varchar(255) default null, ... `title` varchar(255) default null, `rating` char(8) default null, ..., primary key(`sku`))
将所有的商品存储到一张表,这张表包含所有商品需要的属性,不同的商品根据需要设置不同的属性,这种方法使得商品查询比较简单,并且允许一个查询跨多种商品,但缺点是浪费的空间比较多。
提取公共属性,多表继承
create table `product` ( `sku` char(8) not null, `title` varchar(255) default null, `description` varchar(255) default null, `price`, ... primary key(`sku`)) create table `product_audio_album` ( `sku` char(8) not null, ... `artist` varchar(255) default null, `genre_0` varchar(255) default null, `genre_1` varchar(255) default null, ..., primary key(`sku`), foreign key(`sku`) references `product`(`sku`)) ... create table `product_film` ( `sku` char(8) not null, ... `title` varchar(255) default null, `rating` char(8) default null, ..., primary key(`sku`), foreign key(`sku`) references `product`(`sku`)) ...
上述方案将所有商品公共的属性提取出来,将公共属性存储到一张表里,每种商品根据自身的需要创建新的表,新表里只存储该商品特有的信息。
entity attribute values 形式存储
所有的数据按照 的3元组的形式存储,这个方案实际上是把关系型数据库当kv存储使用,模型简单,但应对复杂的查询不是很方便。
entity attribute values
sku_00e8da9b type audio album
sku_00e8da9b title a love supreme
sku_00e8da9b … …
sku_00e8da9b artist john coltrane
sku_00e8da9b genre jazz
sku_00e8da9b genre general
… … …
mongodb 解决方案
mognodb 与关系型数据库不同,其无schema,文档内容可以非常灵活的定制,能很好的使用上述商品分类存储的需求; 将商品信息存储在一个集合里,集合里不同的商品可以自定义文档内容。
比如一个音乐专辑可以类似如下的文档结构
{ sku: "00e8da9b", type: "audio album", title: "a love supreme", description: "by john coltrane", asin: "b0000a118m", shipping: { weight: 6, dimensions: { width: 10, height: 10, depth: 1 }, }, pricing: { list: 1200, retail: 1100, savings: 100, pct_savings: 8 }, details: { title: "a love supreme [original recording reissued]", artist: "john coltrane", genre: [ "jazz", "general" ], ... tracks: [ "a love supreme part i: acknowledgement", "a love supreme part ii - resolution", "a love supreme, part iii: pursuance", "a love supreme, part iv-psalm" ], }, }
而一部电影则可以存储为
{ sku: "00e8da9d", type: "film", ..., asin: "b000p0j0aq", shipping: { ... }, pricing: { ... }, details: { title: "the matrix", director: [ "andy wachowski", "larry wachowski" ], writer: [ "andy wachowski", "larry wachowski" ], ..., aspect_ratio: "1.66:1" }, }
所有商品都拥有一些共同的基本信息,特定的商品可以根据需要扩展独有的内容,非常方便; 基于上述模型,mongodb 也能很好的服务各类查询。
查询某个演员参演的所有电影,并按发型日志排序
db.products.find({'type': 'film', 'details.actor': 'keanu reeves'}).sort({'details.issue_date', -1})
上述查询也可以通过建立索引来加速
db.products.createindex({ type: 1, 'details.actor': 1, 'details.issue_date': -1 })
查询标题里包含特定信息的所有电影
db.products.find({ 'type': 'film', 'title': {'$regex': '.*hacker.*', '$options':'i'}}).sort({'details.issue_date', -1})
可建立如下索引来加速查询
db.products.createindex({ type: 1, details.issue_date: -1, title: 1 })
扩展
当单个节点无法满足海量商品信息存储的需求时,就需要使用mongodb sharding来扩展,假定大量的查询都是都会基于商品类型,那么就可以使用商品类型字段来进行分片。
db.shardcollection('products', { key: {type: 1} })
分片时,尽量使用复合的索引字段,这样能满足更多的查询需求,比如基于商品类型之后,还会经常根据商品的风格标签来查询,则可以把商品的标签字段作为第二分片key。
db.shardcollection('products', { key: {type: 1, 'details.genre': 1} })
如果某种类型的商品,拥有相同标签的特别多,则会出现jumbo chunk的问题,导致无法迁移,可以进一步的优化分片key,以避免这种情况。
db.shardcollection('products', { key: {type: 1, 'details.genre': 1, sku: 1} })
加入第3分片key之后,即使类型、风格标签都相同,但其sku信息肯定不同,就肯定不会出现超大的chunk。
