隨著業(yè)務(wù)越來(lái)越看重?cái)?shù)據(jù)的重要性,相信大家也做了很多
多維分析的需求,在調(diào)研技術(shù)選型時(shí)候,會(huì)發(fā)現(xiàn)很多olap,如druid、clickhouse、starRocks都是列式存儲(chǔ)數(shù)據(jù)庫(kù),今天我們來(lái)通過(guò)對(duì)比行存儲(chǔ)簡(jiǎn)單說(shuō)下列存儲(chǔ)
舉個(gè)栗子來(lái)說(shuō)下列存儲(chǔ)和行存儲(chǔ)
要過(guò)年回家了,今天做了核酸檢測(cè),我們就以存儲(chǔ)核酸檢測(cè)為業(yè)務(wù)場(chǎng)景
栗子

- 就以應(yīng)用頁(yè)面為主,存儲(chǔ)核酸記錄需要存儲(chǔ)以下幾個(gè)字段
-
姓名、身份證號(hào)、檢測(cè)機(jī)構(gòu)、檢測(cè)時(shí)間、結(jié)果、價(jià)格
行存儲(chǔ)
在行存儲(chǔ)下(如mysql等),大家都比較熟悉,就不多說(shuō)了,每條記錄都是表中的每一行,表存儲(chǔ)如下
|姓名|身份證號(hào)|檢測(cè)機(jī)構(gòu)|檢測(cè)時(shí)間|結(jié)果|價(jià)格|
|-----|-----|--|----------|-----|-----|
| 彥祖 | 123512387 | 北京 | 2021-12-24 12:12:45 | 陰性 |35|
| 德華 | 213124157 | 上海 | 2021-12-22 12:12:45 | 陰性 |20|
| 路人甲 | 213123145 | 河南 | 2021-12-21 12:12:45 | 陰性 | 8 |
| 德華 | 213124157 | 廣州 | 2021-12-29 12:12:45 | 陰性 | 23|
| 彥祖 | 123512387 | 上海 | 2021-12-30 12:12:45 | 陰性 | 20|-
因?yàn)閿?shù)據(jù)都是按行方式存儲(chǔ),所以在物理存儲(chǔ)中,會(huì)以連續(xù)空間來(lái)存儲(chǔ)數(shù)據(jù),物理存儲(chǔ)中,這些數(shù)據(jù)存儲(chǔ)方式如下:
行存儲(chǔ)物理結(jié)構(gòu)
行存儲(chǔ)優(yōu)點(diǎn)分析
- 在這樣的物理結(jié)構(gòu)下,因?yàn)槭沁B續(xù)空間,所以插入一條數(shù)據(jù)只需要追加到當(dāng)前數(shù)據(jù)之后即可,很方便
- 對(duì)于按記錄查詢也很方便,例如:我們要查詢彥祖的所有核酸記錄,頁(yè)面應(yīng)用的話應(yīng)該是通過(guò)彥祖的身份證號(hào)
- 對(duì)應(yīng)的sql如下:
select * from 核酸記錄表 where 身份證號(hào)='彥祖的身份證號(hào)'
- 這個(gè)sql的執(zhí)行流程比較清晰
1.先從索引查詢出來(lái)彥祖的記錄存儲(chǔ)的物理地址
2.在通過(guò)物理地址去表的物理存儲(chǔ)中查詢對(duì)應(yīng)地址中的數(shù)據(jù)
- 這樣就可以快速得到彥祖的核酸記錄
行存儲(chǔ)缺點(diǎn)分析
- 這時(shí)候,業(yè)務(wù)方提了一個(gè)需求,他要統(tǒng)計(jì)彥祖做核酸總共花了多少錢
- 對(duì)于這個(gè)需求,sql實(shí)現(xiàn)也很簡(jiǎn)單,通過(guò)對(duì)
價(jià)格列sum就可以實(shí)現(xiàn),sql如下:
select sum(價(jià)格) from 核酸記錄表 where 身份證號(hào)='彥祖的身份證號(hào)'
- 這個(gè)sql的執(zhí)行流程也比較清晰
1.先從索引查詢出來(lái)彥祖的記錄存儲(chǔ)的物理地址
2.在通過(guò)物理地址去表的物理存儲(chǔ)中查詢對(duì)應(yīng)地址中的數(shù)據(jù)
3.拿到所有數(shù)據(jù)時(shí)候,在通過(guò)對(duì)于價(jià)格列sun聚合得到結(jié)果
- 分析下,因?yàn)樾写鎯?chǔ)使用的是連續(xù)空間,即使需求里面只需要
select sum(價(jià)格),但是讀取物理存儲(chǔ)時(shí)候,還是讀取出來(lái)了所有的字段
行存儲(chǔ)優(yōu)缺點(diǎn)總結(jié)
- 通過(guò)上面的分析,總結(jié)一下行存儲(chǔ)的優(yōu)缺點(diǎn)
-
優(yōu)點(diǎn):
1.連續(xù)空間對(duì)于插入/更新很方便
2.對(duì)于記錄查詢很方便
缺點(diǎn)
1.會(huì)查詢出來(lái)很多不需要的列
列存儲(chǔ)
姓名 |
身份證號(hào) |
檢測(cè)機(jī)構(gòu) |
檢測(cè)時(shí)間 |
結(jié)果 |
價(jià)格 |
|---|---|---|---|---|---|
| 彥祖 | 123512387 | 北京 | 2021-12-24 12:12:45 | 陰性 | 35 |
| 德華 | 213124157 | 上海 | 2021-12-22 12:12:45 | 陰性 | 20 |
| 路人甲 | 213123145 | 河南 | 2021-12-21 12:12:45 | 陰性 | 8 |
| 德華 | 213124157 | 廣州 | 2021-12-29 12:12:45 | 陰性 | 23 |
| 彥祖 | 123512387 | 上海 | 2021-12-30 12:12:45 | 陰性 | 20 |
- 在列存儲(chǔ)中,對(duì)于同樣的核酸記錄表,存儲(chǔ)的物理結(jié)構(gòu)如下:

- 在列式存儲(chǔ)中,會(huì)把每一列存儲(chǔ)到一起,如
姓名列,是把所有記錄中的姓名這列的值使用連續(xù)空間存放到一起 - 而對(duì)于各個(gè)列之間,是沒有必要使用連續(xù)空間存放到一起的,所以很多列式數(shù)據(jù)庫(kù)都使用了分布式存儲(chǔ)的方式,存儲(chǔ)各個(gè)列
- 下面我們來(lái)分析下列存儲(chǔ)的
數(shù)據(jù)壓縮和查詢執(zhí)行流程
列存儲(chǔ)的數(shù)據(jù)壓縮
- 很多列式數(shù)據(jù)庫(kù)都是通過(guò)
字典表的方式進(jìn)行數(shù)據(jù)壓縮 - 因?yàn)槭前衙恳涣写娣诺揭黄鸬?,所以很容易通過(guò)對(duì)于每一列進(jìn)行去重,來(lái)構(gòu)建一個(gè)字典表,例如:
- 對(duì)于
姓名列,這列的所有數(shù)據(jù)如下:
彥祖|德華|路人甲|德華|彥祖
- 對(duì)這列值去重以后,構(gòu)建一張
姓名列字典表,構(gòu)建算法忽略,就使用自增id的方式,如下:
|id|姓名列|
|-----|-----|
|1| 彥祖 |
|2| 德華 |
|3| 路人甲 | - 這樣構(gòu)建字典表,對(duì)于列存儲(chǔ)的物理存儲(chǔ)結(jié)構(gòu),就可以執(zhí)行存儲(chǔ)字典表中的id,而不用存儲(chǔ)具體的值,有了字典表以后
姓名列存儲(chǔ)如下:
1|2|3|2|1
- 同樣對(duì)于
價(jià)格列,這列的所有數(shù)據(jù)如下:
35|20|8|23|20
對(duì)這列值去重以后,構(gòu)建一張
價(jià)格列字典表,構(gòu)建算法忽略,就使用自增id的方式,如下:
|id|價(jià)格列|
|-----|-----|
|1| 35 |
|2| 20 |
|3| 8 |
|4|23|有了字典表以后
價(jià)格列存儲(chǔ)如下:
1|2|3|4|2
- 這樣通過(guò)一些數(shù)據(jù)壓縮算法等,可以對(duì)數(shù)據(jù)存儲(chǔ)進(jìn)行壓縮
列存儲(chǔ)的查詢執(zhí)行過(guò)程
- 有字典表以后,我們來(lái)看下,列存儲(chǔ)一般是如何進(jìn)行查詢的
- 業(yè)務(wù)需求查詢
彥祖,20塊錢做的核酸記錄:
select * from 核酸記錄表 where 姓名=彥祖 and 價(jià)格=20
- 對(duì)于該sql,執(zhí)行過(guò)程如下:
1.對(duì)于where 姓名=彥祖
首先查詢姓名字典表,查詢到彥祖的id=1
id |
姓名列 |
|---|---|
| 1 | 彥祖 |
| 2 | 德華 |
| 3 | 路人甲 |
2.通過(guò)查詢到彥祖的id,對(duì)于性名列進(jìn)行對(duì)比,構(gòu)建一個(gè)bitmap,把匹配的要的列的索引位設(shè)置為1,否則為0

3.對(duì)于where 價(jià)格=20
和上面一樣的操作,先查詢價(jià)格字段表,20的id=2
id |
價(jià)格列 |
|---|---|
| 1 | 35 |
| 2 | 20 |
| 3 | 8 |
| 4 | 23 |
4.通過(guò)查詢到價(jià)格20的id,對(duì)于價(jià)格列進(jìn)行對(duì)比,構(gòu)建一個(gè)bitmap,把匹配的要的列的索引位設(shè)置為1,否則為0

5.對(duì)于兩個(gè)where條件的結(jié)果bitmap做與運(yùn)算,bitmap中,位為1的索引就是要查詢數(shù)據(jù)的所有列的索引,如該栗子中,兩個(gè)結(jié)果bitmap與運(yùn)算后的結(jié)果是00001,所以所有列的第5個(gè)值,拼接起來(lái)就是我們要查詢的數(shù)據(jù)

6.所以我們把所有列的第五個(gè)值拿出來(lái)組裝后就是我們需要的數(shù)據(jù)
列存儲(chǔ)優(yōu)點(diǎn)分析
- 上面講了列存儲(chǔ)的
數(shù)據(jù)壓縮,在數(shù)據(jù)壓縮上列存儲(chǔ)有一定的優(yōu)勢(shì) - 每一列都可以天然做索引,不需要額外的數(shù)據(jù)結(jié)構(gòu)來(lái)對(duì)各個(gè)列構(gòu)建索引,所以不用在意每一列的數(shù)據(jù)類型,都可以做索引
- 對(duì)于統(tǒng)計(jì)彥祖做核酸總共花了多少錢這種需求
select sum(價(jià)格) from 核酸記錄表 where 身份證號(hào)='彥祖的身份證號(hào)'
- 因?yàn)榱惺?code>分開存儲(chǔ)的,按照上面講的查詢流程,其實(shí)最后我們得到的
結(jié)果bitmap,拿到位=1的索引后,我們不需要查詢所有的列,只需要拿著索引去價(jià)格列中獲取相應(yīng)位置的值,然后在進(jìn)行sum聚合
列存儲(chǔ)缺點(diǎn)分析
- 因?yàn)楦鱾€(gè)列是分開存儲(chǔ)的,所以在插入、更新時(shí),需要對(duì)于
每一個(gè)列進(jìn)行操作,沒有行存儲(chǔ)連續(xù)空間那么方便 - 還是看上面說(shuō)的查詢過(guò)程,每次查詢過(guò)后,都需要對(duì)查詢到的需要的列進(jìn)行一個(gè)數(shù)據(jù)組裝
列存儲(chǔ)優(yōu)缺點(diǎn)總結(jié)
- 通過(guò)上面的分析,總結(jié)一下列存儲(chǔ)的優(yōu)缺點(diǎn)
-
優(yōu)點(diǎn):
1.數(shù)據(jù)壓縮比較有優(yōu)勢(shì)
2.任何列都可以做索引
3.查詢時(shí)只有涉及到的列會(huì)被讀取
缺點(diǎn)
1.每次查詢時(shí),都需要對(duì)查詢到的列進(jìn)行數(shù)據(jù)重新組裝
2.插入/更新操作比較困難
列式存儲(chǔ)數(shù)據(jù)庫(kù)就說(shuō)到這里,歡迎大家來(lái)交流,指出文中一些說(shuō)錯(cuò)的地方,讓我加深認(rèn)識(shí),愿大家沒有bug,謝謝!
