導讀:在講《Apache Druid 底層存儲設計》時就說過要講一講列式存儲。現在來了,通過本文你可以了解到行存儲模式、列存儲模式、它們的優缺點以及列存儲模式的優化等知識。
今日格言:不要局限於單向思維,多對比了解更多不同維度的東西。
從數據存儲講起
我們最先接觸的數據庫系統,大部分都是行存儲系統。大學的時候學數據庫,老師讓我們將數據庫想象成一張表格,每條數據記錄就是一行數據,每行數據包含若干列。所以我們對大部分數據存儲的思維也就是一個復雜一點的表格管理系統。我們在一行一行地寫入數據,然后按查詢條件查詢過濾出我們想要的行記錄。
大部分傳統的關系型數據庫,都是面向行來組織數據的。如 Mysql,Postgresql。近幾年,也越來越多傳統數據庫加入了列存儲的能力。雖然列存儲的技術在十幾年前就已經出現,卻從來沒有像現在這樣成為一種流行的存儲組織方式。
行存儲和列存儲,是數據庫底層組織數據的方式。(和文檔型、K-V 型,時序型等概念不在一個層次)
行存儲
行存儲系統以行的方式來組織數據。假設現在有以下 blog 數據(大學時老師布置系統課題作業總是讓我們做一個博客系統,大概因為他們最先接觸的互聯網就是 BBS 吧):
[
{
"title": "Oriented Column Store",
"author": "Alex",
"publish_time": 1508423456,
"like_num": 1024
},{
"title": "Apache Druid",
"author": "Bob",
"publish_time": 1504423069,
"like_num": 10
},{
"title": "Algorithm",
"author": "Casey",
"publish_time": 1512523069,
"like_num": 16
}
]

行存儲將會以下列方式將數據存儲在磁盤上。我們可以思考一下,這樣的方式利於什么樣的存儲?(此處停頓 5 秒思考一下)它利於數據一行一行的寫入,寫入一條數據記錄時,只需要將數據追加到已有數據記錄后面即可。
行模式存儲適合 OLTP(Online Transaction Processing)系統。因為數據基於行存儲,所以數據的寫入會更快。對按記錄查詢數據也更簡單。
大部分同學會問,我們的做的系統不就是在為了這個嗎?所以我為什么還需要列式存儲,而列式存儲又是什么?
讓我們想象一種場景,現在不是想查詢 Bob 的博客,我想統計 Bob 發表的博客數,或是整個系統今天的博客點贊數。如果是行存儲系統,數據庫將怎樣操作?(停頓思考 10 秒)

如圖,想統計所有點贊數,首先需要將所有行數據讀入內存,然后對 like_num 列做 sum 操作,從而得到結果。我們假設磁盤一次可以讀取圖中 3 個方框的數據(實際需要按 byte 來讀取),那么這個聚合計算需要 N(N=數據量)次磁盤訪問。
這種經常需要通過大量數據集來聚合統計數據的需求其實是 OLAP 系統的常見行為。基於這個需求我們也可以明白為什么這幾年列式存儲開始流行。因為數據,大數據,數據分析,也就是 OLAP(Online Analytical Processing)在線分析系統的需求增多了,數據寫入的事務和按記錄查詢數據都不是它的關注點,它關注的是數據過濾,統計。
列存儲
同樣是上面的示例數據,我們來看列式存儲是怎樣組織數據的。
[
{
"title": "Oriented Column Store",
"author": "Alex",
"publish_time": 1508423456,
"like_num": 1024
},{
"title": "Apache Druid",
"author": "Bob",
"publish_time": 1504423069,
"like_num": 10
},{
"title": "Algorithm",
"author": "Casey",
"publish_time": 1512523069,
"like_num": 16
}
]

如圖所示,列式存儲將每一列的數據組織在一起。可以思考一下這樣利於什么呢?(停頓 5 秒)
是的,利於對於列的操作,如上面我們說到的統計所有 like_num 之和。其過程將如下:

依然假設磁盤一次可以讀取 3 個方框的數據(實際按 byte 讀取)。可以看出按列存儲組織數據的方式,只需要 1 次磁盤操作就可以完成。
在程序的世界里,我們學會了,任何的選擇和傾向都是有代價的。空間換時間,時間換空間,一致性可用性相互平衡等。選擇列式存儲必然也有不利的一面。首先就表現在數據寫入上。

當一條新數據到來,需要將每一列存儲到對應的位置。這樣就需要多次寫磁盤操作。(當然真實的數據庫不會出現圖中”擠一擠“、”挪一挪“的情況,數據庫會將不同列數據組織在不同的地方;對於多次寫操作的問題,大部分存儲系統會通過緩沖來降低這種情況帶來的不足)
對比
| Row-Store | Column-Store |
|---|---|
| 因為按一行一行寫和讀取數據,因此讀取數據時往往需要讀取那些不必要的列 | 可以只讀取必要的列 |
| 易於按記錄讀寫數據 | 對一個一個記錄的數據寫入和讀取都較慢 |
| 適合 OLTP 系統 | 適合 OLAP 系統 |
| 不利於大數據集的聚合統計操作 | 利於大數據集的數據聚合操作 |
| 不利於壓縮數據 | 利於壓縮數據 |
列存儲優勢
基於列模式的存儲,天然就會具備以下幾個優點:
-
自動索引
因為基於列存儲,所以每一列本身就相當於索引。所以在做一些需要索引的操作時,就不需要額外的數據結構來為此列創建合適的索引。
-
利於數據壓縮
利於壓縮有兩個原因。一來你會發現大部分列數據基數其實是重復的,拿上面的數據來說,因為同一個 author 會發表多篇博客,所以 author 列出現的所有值的基數肯定是小於博客數量的,因此在 author 列的存儲上其實是不需要存儲博客數量這么大的數據量的;二來相同的列數據類型一致,這樣利於數據結構填充的優化和壓縮,而且對於數字列這種數據類型可以采取更多有利的算法去壓縮存儲。
最后
目前列存儲模式在很多分析型數據庫中都很常見。而且因為大數據分析型需求的增多,越來越多傳統的行存儲數據庫也加入了列存儲的模式,比如 Oracle 和 Sql Server 都有了列存儲的特性。
之前講的 Apache Druid 底層數據存儲就是基於列模式。有興趣的可以回顧一下。另外 HBase 是一個比較有代表性的列存儲模式數據庫。有時間可以來聊一聊 HBase 底層是如何存儲數據的。也可以講一講數字列的壓縮方式(大家也可以先思考一下可以如何壓縮數字列)。
系列文章:
系列推薦
Mysql:小主鍵,大問題
Mysql大數據量問題與解決
你應該知道一些其他存儲——列式存儲
時間序列數據庫(TSDB)初識與選擇(InfluxDB、OpenTSDB、Druid、Elasticsearch對比)
十分鍾了解Apache Druid(集數據倉庫、時間序列、全文檢索於一體的存儲方案)
Apache Druid 底層存儲設計(列存儲與全文檢索)
Apache Druid 的集群設計與工作流程
參考文章:
https://towardsdatascience.com/the-beauty-of-column-oriented-data-2945c0c9f560
https://dataschool.com/data-modeling-101/row-vs-column-oriented-databases/
想了解更多數據存儲相關知識,請關注我的公眾號。

