【摘要】電商業務一個基本的功能模塊就是存儲品類豐富的商品信息,各種商品特性、參數各異,MongoDB 靈活的文檔模型非常適合於這類業務,本文主要介紹如何使用 MongoDB 來存儲商品分類信息,內容翻譯自User case - Product Catalog 關係型數據庫解決方案 上述問題使用傳統的關係型數據庫也可以解決,比如以下幾種方案 針對不同商品,創建不同的表 比如音樂專輯、電影這2種商品,有一部分共同的屬性,但也有很多自身特有的屬性,可以創建2個不同的表,擁有不同的schema。
電商業務一個基本的功能模塊就是存儲品類豐富的商品信息,各種商品特性、參數各異,MongoDB 靈活的文檔模型非常適合於這類業務,本文主要介紹如何使用 MongoDB 來存儲商品分類信息,內容翻譯自User case - Product Catalog。
關係型數據庫解決方案
上述問題使用傳統的關係型數據庫也可以解決,比如以下幾種方案:
針對不同商品,創建不同的表
比如音樂專輯、電影這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形式存儲
所有的數據按照<商品SKU, 屬性、值> 的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。
作者:張友東,花名林青,阿里雲數據庫組技術專家,主要關注分佈式存儲、NoSQL數據庫等技術領域,目前主要參與MongoDB雲數據庫的研發,致力於讓開發者用上最好的MongoDB雲服務。