億級數(shù)據(jù)分析性能殺手锏 - 列式存儲
看似非常簡單的一條 SQL,如下圖,我們求解的無非就是月總銷售量
- SELECT OrderMonth, SUM( OrderAmount) AS Amount
- FROM FctOrderSales WITH(NOLOCK)
- WHERE OrderMonth BETWEEN '2017-01-01' AND '2018-12-31'
- GROUP BY OrderMonth
一旦 FctOrderSales 的數(shù)據(jù)量增長,恐怕不等個半把個小時,是出不來的。
再加上,我們的用戶不會那么輕易的放過“折磨”我們 IT 工程師的機(jī)會,就僅僅圍繞月份來做統(tǒng)計,可能還會加上,產(chǎn)品類型,地區(qū)等等維度。這樣一來,就更加麻煩了,眼珠子恨不得貼到屏幕上,一分一秒的看著時間的流逝,可惜了青春啊~
事實上,大家肯定會出一定的方案來解決這種效率奇慢的查詢,比如:
1 加個索引
2 加個分區(qū)
3 ETL 先算好聚合數(shù)據(jù)
4 。。。
解決方案總是有的。在這里我們來看看另一種玩法,列式存儲。
上圖中,表格中的數(shù)據(jù),就是典型的row-based data page 行式存儲數(shù)據(jù)頁。一行相鄰一行的存儲在一個數(shù)據(jù)頁上,一列肩并著一列存儲在一行上。而 Columnar Storage Layout 就是列式存儲, 每一個列的數(shù)據(jù)都被存在一個數(shù)據(jù)文件中,比如date_key 按照順序存儲在 date_key file 中,Product_sk 也一樣,按照二維表中的順序,存儲在 product_sk 文件中。每個列式存儲文件中,對應(yīng)行號上存儲的數(shù)據(jù),都是表結(jié)構(gòu)中相應(yīng)行號的列數(shù)據(jù)。即要獲取原表結(jié)構(gòu)中,第20行的數(shù)據(jù),那么要分別從這些列式存儲的文件中,取得各自的第20行數(shù)據(jù),集合起來!
考慮以下場景,比如我們就要分析 2013年,每個月,某兩個品種,69,31 的銷量:
SELECT
getMonth(date_Key) AS Month
- getProductName(product_sk) AS Product
- SUM(quantity) AS Quantity
FROM FctSalesOrdinary
GROUP BY getMonth(date_Key), getProductName(product_sk)
在這里,讓我們做出這些假設(shè):
1 date_key是存在一個數(shù)據(jù)文件里面的,product_sk 是存在另一個數(shù)據(jù)文件里面的。2013年整年的銷售數(shù)據(jù)有 200W 條,每個文件的段能存儲100W,(按照 SQL Server 的存儲容量來計算),這樣就總共抓取 2個段, 按照每次讀取一個segment的機(jī)制,連續(xù)讀取 2個連續(xù)段就只有一次讀取,因此磁頭讀取不需要再次尋址。
2 假設(shè)2013年***條數(shù)據(jù)在源數(shù)據(jù)表中,是第100萬條數(shù)據(jù),2013年***一條數(shù)據(jù)是第299萬條數(shù)據(jù)。
按照上面的圖解釋,我們讀取2013年數(shù)據(jù)的時候,讀取product_sk的字段,即讀取 第100萬條數(shù)據(jù)到 299萬條數(shù)據(jù),然后根據(jù)product_sk做限制。比起row-based data page(行式存儲數(shù)據(jù)頁),其他字段store,promotion,customer字段統(tǒng)統(tǒng)舍去,少讀了很多的無效數(shù)據(jù)。
列式存儲還帶有一個壓縮的選項。因為每個列式存儲文件都是存儲的同質(zhì)數(shù)據(jù),那么對這些同質(zhì)數(shù)據(jù)進(jìn)行壓縮,會有很好的壓縮效率。壓縮帶來的好處是,減少了吞吐量,使得內(nèi)存容下了更多的數(shù)據(jù),并且還可以有效使用 CPU L1 Cache, 這種技術(shù)稱為 vectorized processing.
*參考The Design and Implementation of Modern Column-Oriented Database systems.
為什么列式存儲更適合分析性數(shù)據(jù)倉庫:
1. 分析決定了一定是讀取大范圍連續(xù)屬性的數(shù)據(jù)。不是隨機(jī)讀,而是順序讀,速度快很多
2. 請求基本上是采取多個維度同時讀的方式,而不會讀取所有的列。這樣很多row-based data page (行式存儲數(shù)據(jù)頁)都會舍去不必要的相鄰列的數(shù)據(jù)。
3. 列式存儲的機(jī)制:一個數(shù)據(jù)文件中單獨(dú)存儲的是整個列的數(shù)據(jù),按照segment來分段,一次至少讀取一個段。一個段中可以存儲大量的同質(zhì)數(shù)據(jù)。
目前支持列式存儲的數(shù)據(jù)有:
- Greenplum
- PostgreSQL
- MariaDB
- Microsoft Azure SQL Data Warehouse
- Microsoft SQL Server 2012 及以上
- BIRT Analytics ColumnarDB
- IBM Db2
- Oracle Database/Exadata
- SAP HANA
- TeraData
- Apache HBase
- ClickHouse
- Apache Parquet
以上是我們常用的數(shù)據(jù)庫品牌,還有一些小眾數(shù)據(jù)庫,比如 MonetDB,kdb+ 等也支持。所以趁早用起來吧