Doris?數(shù)據(jù)模型ROLLUP及前綴索引官方教程
基本概念
在 Doris 中,數(shù)據(jù)以表(Table)的形式進(jìn)行邏輯上的描述。
一張表包括行(Row)和列(Column)。Row 即用戶的一行數(shù)據(jù)。Column 用于描述一行數(shù)據(jù)中不同的字段。
Column 可以分為兩大類:Key 和 Value。從業(yè)務(wù)角度看,Key 和 Value 可以分別對(duì)應(yīng)維度列和指標(biāo)列。
Doris 的數(shù)據(jù)模型主要分為3類:
- Aggregate
- Uniq
- Duplicate
下面我們分別介紹。
Aggregate 模型(聚合模型)
我們以實(shí)際的例子來說明什么是聚合模型,以及如何正確的使用聚合模型。
示例1:導(dǎo)入數(shù)據(jù)聚合
假設(shè)業(yè)務(wù)有如下數(shù)據(jù)表模式:
ColumnName | Type | AggregationType | Comment |
---|---|---|---|
user_id | LARGEINT | 用戶id | |
date | DATE | 數(shù)據(jù)灌入日期 | |
city | VARCHAR(20) | 用戶所在城市 | |
age | SMALLINT | 用戶年齡 | |
sex | TINYINT | 用戶性別 | |
last_visit_date | DATETIME | REPLACE | 用戶最后一次訪問時(shí)間 |
cost | BIGINT | SUM | 用戶總消費(fèi) |
max_dwell_time | INT | MAX | 用戶最大停留時(shí)間 |
min_dwell_time | INT | MIN | 用戶最小停留時(shí)間 |
如果轉(zhuǎn)換成建表語句則如下(省略建表語句中的 Partition 和 Distribution 信息)
CREATE TABLE IF NOT EXISTS example_db.expamle_tbl ( `user_id` LARGEINT NOT NULL COMMENT "用戶id", `date` DATE NOT NULL COMMENT "數(shù)據(jù)灌入日期時(shí)間", `city` VARCHAR(20) COMMENT "用戶所在城市", `age` SMALLINT COMMENT "用戶年齡", `sex` TINYINT COMMENT "用戶性別", `last_visit_date` DATETIME REPLACE DEFAULT "1970-01-01 00:00:00" COMMENT "用戶最后一次訪問時(shí)間", `cost` BIGINT SUM DEFAULT "0" COMMENT "用戶總消費(fèi)", `max_dwell_time` INT MAX DEFAULT "0" COMMENT "用戶最大停留時(shí)間", `min_dwell_time` INT MIN DEFAULT "99999" COMMENT "用戶最小停留時(shí)間", ) AGGREGATE KEY(`user_id`, `date`, `timestamp`, `city`, `age`, `sex`) ... /* 省略 Partition 和 Distribution 信息 */ ;
可以看到,這是一個(gè)典型的用戶信息和訪問行為的事實(shí)表。
在一般星型模型中,用戶信息和訪問行為一般分別存放在維度表和事實(shí)表中。這里我們?yōu)榱烁臃奖愕慕忉?Doris 的數(shù)據(jù)模型,將兩部分信息統(tǒng)一存放在一張表中。
表中的列按照是否設(shè)置了 AggregationType
,分為 Key (維度列) 和 Value(指標(biāo)列)。沒有設(shè)置 AggregationType
的,如 user_id
、date
、age
... 等稱為 Key,而設(shè)置了 AggregationType
的稱為 Value。
當(dāng)我們導(dǎo)入數(shù)據(jù)時(shí),對(duì)于 Key 列相同的行和聚合成一行,而 Value 列會(huì)按照設(shè)置的 AggregationType
進(jìn)行聚合。 AggregationType
目前有以下四種聚合方式:
- SUM:求和,多行的 Value 進(jìn)行累加。
- REPLACE:替代,下一批數(shù)據(jù)中的 Value 會(huì)替換之前導(dǎo)入過的行中的 Value。
- MAX:保留最大值。
- MIN:保留最小值。
假設(shè)我們有以下導(dǎo)入數(shù)據(jù)(原始數(shù)據(jù)):
user_id | date | city | age | sex | last_visit_date | cost | max_dwell_time | min_dwell_time |
---|---|---|---|---|---|---|---|---|
10000 | 2017-10-01 | 北京 | 20 | 0 | 2017-10-01 06:00:00 | 20 | 10 | 10 |
10000 | 2017-10-01 | 北京 | 20 | 0 | 2017-10-01 07:00:00 | 15 | 2 | 2 |
10001 | 2017-10-01 | 北京 | 30 | 1 | 2017-10-01 17:05:45 | 2 | 22 | 22 |
10002 | 2017-10-02 | 上海 | 20 | 1 | 2017-10-02 12:59:12 | 200 | 5 | 5 |
10003 | 2017-10-02 | 廣州 | 32 | 0 | 2017-10-02 11:20:00 | 30 | 11 | 11 |
10004 | 2017-10-01 | 深圳 | 35 | 0 | 2017-10-01 10:00:15 | 100 | 3 | 3 |
10004 | 2017-10-03 | 深圳 | 35 | 0 | 2017-10-03 10:20:22 | 11 | 6 | 6 |
我們假設(shè)這是一張記錄用戶訪問某商品頁面行為的表。我們以第一行數(shù)據(jù)為例,解釋如下:
數(shù)據(jù) | 說明 |
---|---|
10000 | 用戶id,每個(gè)用戶唯一識(shí)別id |
2017-10-01 | 數(shù)據(jù)入庫(kù)時(shí)間,精確到日期 |
北京 | 用戶所在城市 |
20 | 用戶年齡 |
0 | 性別男(1 代表女性) |
2017-10-01 06:00:00 | 用戶本次訪問該頁面的時(shí)間,精確到秒 |
20 | 用戶本次訪問產(chǎn)生的消費(fèi) |
10 | 用戶本次訪問,駐留該頁面的時(shí)間 |
10 | 用戶本次訪問,駐留該頁面的時(shí)間(冗余) |
那么當(dāng)這批數(shù)據(jù)正確導(dǎo)入到 Doris 中后,Doris 中最終存儲(chǔ)如下:
user_id | date | city | age | sex | last_visit_date | cost | max_dwell_time | min_dwell_time |
---|---|---|---|---|---|---|---|---|
10000 | 2017-10-01 | 北京 | 20 | 0 | 2017-10-01 07:00:00 | 35 | 10 | 2 |
10001 | 2017-10-01 | 北京 | 30 | 1 | 2017-10-01 17:05:45 | 2 | 22 | 22 |
10002 | 2017-10-02 | 上海 | 20 | 1 | 2017-10-02 12:59:12 | 200 | 5 | 5 |
10003 | 2017-10-02 | 廣州 | 32 | 0 | 2017-10-02 11:20:00 | 30 | 11 | 11 |
10004 | 2017-10-01 | 深圳 | 35 | 0 | 2017-10-01 10:00:15 | 100 | 3 | 3 |
10004 | 2017-10-03 | 深圳 | 35 | 0 | 2017-10-03 10:20:22 | 11 | 6 | 6 |
可以看到,用戶 10000 只剩下了一行聚合后的數(shù)據(jù)。而其余用戶的數(shù)據(jù)和原始數(shù)據(jù)保持一致。這里先解釋下用戶 10000 聚合后的數(shù)據(jù):
前5列沒有變化,從第6列 last_visit_date
開始:
2017-10-01 07:00:00
:因?yàn)?nbsp;last_visit_date
列的聚合方式為 REPLACE,所以 2017-10-01 07:00:00
替換了 2017-10-01 06:00:00
保存了下來。
注:在同一個(gè)導(dǎo)入批次中的數(shù)據(jù),對(duì)于 REPLACE 這種聚合方式,替換順序不做保證。如在這個(gè)例子中,最終保存下來的,也有可能是 2017-10-01 06:00:00
。而對(duì)于不同導(dǎo)入批次中的數(shù)據(jù),可以保證,后一批次的數(shù)據(jù)會(huì)替換前一批次。
35
:因?yàn)?nbsp;cost
列的聚合類型為 SUM,所以由 20 + 15 累加獲得 35。10
:因?yàn)?nbsp;max_dwell_time
列的聚合類型為 MAX,所以 10 和 2 取最大值,獲得 10。2
:因?yàn)?nbsp;min_dwell_time
列的聚合類型為 MIN,所以 10 和 2 取最小值,獲得 2。
經(jīng)過聚合,Doris 中最終只會(huì)存儲(chǔ)聚合后的數(shù)據(jù)。換句話說,即明細(xì)數(shù)據(jù)會(huì)丟失,用戶不能夠再查詢到聚合前的明細(xì)數(shù)據(jù)了。
示例2:保留明細(xì)數(shù)據(jù)
接示例1,我們將表結(jié)構(gòu)修改如下:
ColumnName | Type | AggregationType | Comment |
---|---|---|---|
user_id | LARGEINT | 用戶id | |
date | DATE | 數(shù)據(jù)灌入日期 | |
timestamp | DATETIME | 數(shù)據(jù)灌入時(shí)間,精確到秒 | |
city | VARCHAR(20) | 用戶所在城市 | |
age | SMALLINT | 用戶年齡 | |
sex | TINYINT | 用戶性別 | |
last_visit_date | DATETIME | REPLACE | 用戶最后一次訪問時(shí)間 |
cost | BIGINT | SUM | 用戶總消費(fèi) |
max_dwell_time | INT | MAX | 用戶最大停留時(shí)間 |
min_dwell_time | INT | MIN | 用戶最小停留時(shí)間 |
即增加了一列 timestamp
,記錄精確到秒的數(shù)據(jù)灌入時(shí)間。
導(dǎo)入數(shù)據(jù)如下:
user_id | date | timestamp | city | age | sex | last_visit_date | cost | max_dwell_time | min_dwell_time |
---|---|---|---|---|---|---|---|---|---|
10000 | 2017-10-01 | 2017-10-01 08:00:05 | 北京 | 20 | 0 | 2017-10-01 06:00:00 | 20 | 10 | 10 |
10000 | 2017-10-01 | 2017-10-01 09:00:05 | 北京 | 20 | 0 | 2017-10-01 07:00:00 | 15 | 2 | 2 |
10001 | 2017-10-01 | 2017-10-01 18:12:10 | 北京 | 30 | 1 | 2017-10-01 17:05:45 | 2 | 22 | 22 |
10002 | 2017-10-02 | 2017-10-02 13:10:00 | 上海 | 20 | 1 | 2017-10-02 12:59:12 | 200 | 5 | 5 |
10003 | 2017-10-02 | 2017-10-02 13:15:00 | 廣州 | 32 | 0 | 2017-10-02 11:20:00 | 30 | 11 | 11 |
10004 | 2017-10-01 | 2017-10-01 12:12:48 | 深圳 | 35 | 0 | 2017-10-01 10:00:15 | 100 | 3 | 3 |
10004 | 2017-10-03 | 2017-10-03 12:38:20 | 深圳 | 35 | 0 | 2017-10-03 10:20:22 | 11 | 6 | 6 |
那么當(dāng)這批數(shù)據(jù)正確導(dǎo)入到 Doris 中后,Doris 中最終存儲(chǔ)如下:
user_id | date | timestamp | city | age | sex | last_visit_date | cost | max_dwell_time | min_dwell_time |
---|---|---|---|---|---|---|---|---|---|
10000 | 2017-10-01 | 2017-10-01 08:00:05 | 北京 | 20 | 0 | 2017-10-01 06:00:00 | 20 | 10 | 10 |
10000 | 2017-10-01 | 2017-10-01 09:00:05 | 北京 | 20 | 0 | 2017-10-01 07:00:00 | 15 | 2 | 2 |
10001 | 2017-10-01 | 2017-10-01 18:12:10 | 北京 | 30 | 1 | 2017-10-01 17:05:45 | 2 | 22 | 22 |
10002 | 2017-10-02 | 2017-10-02 13:10:00 | 上海 | 20 | 1 | 2017-10-02 12:59:12 | 200 | 5 | 5 |
10003 | 2017-10-02 | 2017-10-02 13:15:00 | 廣州 | 32 | 0 | 2017-10-02 11:20:00 | 30 | 11 | 11 |
10004 | 2017-10-01 | 2017-10-01 12:12:48 | 深圳 | 35 | 0 | 2017-10-01 10:00:15 | 100 | 3 | 3 |
10004 | 2017-10-03 | 2017-10-03 12:38:20 | 深圳 | 35 | 0 | 2017-10-03 10:20:22 | 11 | 6 | 6 |
我們可以看到,存儲(chǔ)的數(shù)據(jù),和導(dǎo)入數(shù)據(jù)完全一樣,沒有發(fā)生任何聚合。這是因?yàn)椋@批數(shù)據(jù)中,因?yàn)榧尤肓?nbsp;timestamp
列,所有行的 Key 都不完全相同。也就是說,只要保證導(dǎo)入的數(shù)據(jù)中,每一行的 Key 都不完全相同,那么即使在聚合模型下,Doris 也可以保存完整的明細(xì)數(shù)據(jù)。
示例3:導(dǎo)入數(shù)據(jù)與已有數(shù)據(jù)聚合
接示例1。假設(shè)現(xiàn)在表中已有數(shù)據(jù)如下:
user_id | date | city | age | sex | last_visit_date | cost | max_dwell_time | min_dwell_time |
---|---|---|---|---|---|---|---|---|
10000 | 2017-10-01 | 北京 | 20 | 0 | 2017-10-01 07:00:00 | 35 | 10 | 2 |
10001 | 2017-10-01 | 北京 | 30 | 1 | 2017-10-01 17:05:45 | 2 | 22 | 22 |
10002 | 2017-10-02 | 上海 | 20 | 1 | 2017-10-02 12:59:12 | 200 | 5 | 5 |
10003 | 2017-10-02 | 廣州 | 32 | 0 | 2017-10-02 11:20:00 | 30 | 11 | 11 |
10004 | 2017-10-01 | 深圳 | 35 | 0 | 2017-10-01 10:00:15 | 100 | 3 | 3 |
10004 | 2017-10-03 | 深圳 | 35 | 0 | 2017-10-03 10:20:22 | 11 | 6 | 6 |
我們?cè)賹?dǎo)入一批新的數(shù)據(jù):
user_id | date | city | age | sex | last_visit_date | cost | max_dwell_time | min_dwell_time |
---|---|---|---|---|---|---|---|---|
10004 | 2017-10-03 | 深圳 | 35 | 0 | 2017-10-03 11:22:00 | 44 | 19 | 19 |
10005 | 2017-10-03 | 長(zhǎng)沙 | 29 | 1 | 2017-10-03 18:11:02 | 3 | 1 | 1 |
那么當(dāng)這批數(shù)據(jù)正確導(dǎo)入到 Doris 中后,Doris 中最終存儲(chǔ)如下:
user_id | date | city | age | sex | last_visit_date | cost | max_dwell_time | min_dwell_time |
---|---|---|---|---|---|---|---|---|
10000 | 2017-10-01 | 北京 | 20 | 0 | 2017-10-01 07:00:00 | 35 | 10 | 2 |
10001 | 2017-10-01 | 北京 | 30 | 1 | 2017-10-01 17:05:45 | 2 | 22 | 22 |
10002 | 2017-10-02 | 上海 | 20 | 1 | 2017-10-02 12:59:12 | 200 | 5 | 5 |
10003 | 2017-10-02 | 廣州 | 32 | 0 | 2017-10-02 11:20:00 | 30 | 11 | 11 |
10004 | 2017-10-01 | 深圳 | 35 | 0 | 2017-10-01 10:00:15 | 100 | 3 | 3 |
10004 | 2017-10-03 | 深圳 | 35 | 0 | 2017-10-03 11:22:00 | 55 | 19 | 6 |
10005 | 2017-10-03 | 長(zhǎng)沙 | 29 | 1 | 2017-10-03 18:11:02 | 3 | 1 | 1 |
可以看到,用戶 10004 的已有數(shù)據(jù)和新導(dǎo)入的數(shù)據(jù)發(fā)生了聚合。同時(shí)新增了 10005 用戶的數(shù)據(jù)。
數(shù)據(jù)的聚合,在 Doris 中有如下三個(gè)階段發(fā)生:
- 每一批次數(shù)據(jù)導(dǎo)入的 ETL 階段。該階段會(huì)在每一批次導(dǎo)入的數(shù)據(jù)內(nèi)部進(jìn)行聚合。
- 底層 BE 進(jìn)行數(shù)據(jù) Compaction 的階段。該階段,BE 會(huì)對(duì)已導(dǎo)入的不同批次的數(shù)據(jù)進(jìn)行進(jìn)一步的聚合。
- 數(shù)據(jù)查詢階段。在數(shù)據(jù)查詢時(shí),對(duì)于查詢涉及到的數(shù)據(jù),會(huì)進(jìn)行對(duì)應(yīng)的聚合。
數(shù)據(jù)在不同時(shí)間,可能聚合的程度不一致。比如一批數(shù)據(jù)剛導(dǎo)入時(shí),可能還未與之前已存在的數(shù)據(jù)進(jìn)行聚合。但是對(duì)于用戶而言,用戶只能查詢到聚合后的數(shù)據(jù)。即不同的聚合程度對(duì)于用戶查詢而言是透明的。用戶需始終認(rèn)為數(shù)據(jù)以最終的完成的聚合程度存在,而不應(yīng)假設(shè)某些聚合還未發(fā)生。(可參閱聚合模型的局限性一節(jié)獲得更多詳情。)
Uniq 模型(唯一主鍵)
在某些多維分析場(chǎng)景下,用戶更關(guān)注的是如何保證 Key 的唯一性,即如何獲得 Primary Key 唯一性約束。因此,我們引入了 Uniq 的數(shù)據(jù)模型。該模型本質(zhì)上是聚合模型的一個(gè)特例,也是一種簡(jiǎn)化的表結(jié)構(gòu)表示方式。我們舉例說明。
ColumnName | Type | IsKey | Comment |
---|---|---|---|
user_id | BIGINT | Yes | 用戶id |
username | VARCHAR(50) | Yes | 用戶昵稱 |
city | VARCHAR(20) | No | 用戶所在城市 |
age | SMALLINT | No | 用戶年齡 |
sex | TINYINT | No | 用戶性別 |
phone | LARGEINT | No | 用戶電話 |
address | VARCHAR(500) | No | 用戶住址 |
register_time | DATETIME | No | 用戶注冊(cè)時(shí)間 |
這是一個(gè)典型的用戶基礎(chǔ)信息表。這類數(shù)據(jù)沒有聚合需求,只需保證主鍵唯一性。(這里的主鍵為 user_id + username)。那么我們的建表語句如下:
CREATE TABLE IF NOT EXISTS example_db.expamle_tbl ( `user_id` LARGEINT NOT NULL COMMENT "用戶id", `username` VARCHAR(50) NOT NULL COMMENT "用戶昵稱", `city` VARCHAR(20) COMMENT "用戶所在城市", `age` SMALLINT COMMENT "用戶年齡", `sex` TINYINT COMMENT "用戶性別", `phone` LARGEINT COMMENT "用戶電話", `address` VARCHAR(500) COMMENT "用戶地址", `register_time` DATETIME COMMENT "用戶注冊(cè)時(shí)間" ) UNIQUE KEY(`user_id`, `user_name`) ... /* 省略 Partition 和 Distribution 信息 */ ;
而這個(gè)表結(jié)構(gòu),完全同等于以下使用聚合模型描述的表結(jié)構(gòu):
ColumnName | Type | AggregationType | Comment |
---|---|---|---|
user_id | BIGINT | 用戶id | |
username | VARCHAR(50) | 用戶昵稱 | |
city | VARCHAR(20) | REPLACE | 用戶所在城市 |
age | SMALLINT | REPLACE | 用戶年齡 |
sex | TINYINT | REPLACE | 用戶性別 |
phone | LARGEINT | REPLACE | 用戶電話 |
address | VARCHAR(500) | REPLACE | 用戶住址 |
register_time | DATETIME | REPLACE | 用戶注冊(cè)時(shí)間 |
及建表語句:
CREATE TABLE IF NOT EXISTS example_db.expamle_tbl ( `user_id` LARGEINT NOT NULL COMMENT "用戶id", `username` VARCHAR(50) NOT NULL COMMENT "用戶昵稱", `city` VARCHAR(20) REPLACE COMMENT "用戶所在城市", `age` SMALLINT REPLACE COMMENT "用戶年齡", `sex` TINYINT REPLACE COMMENT "用戶性別", `phone` LARGEINT REPLACE COMMENT "用戶電話", `address` VARCHAR(500) REPLACE COMMENT "用戶地址", `register_time` DATETIME REPLACE COMMENT "用戶注冊(cè)時(shí)間" ) AGGREGATE KEY(`user_id`, `user_name`) ... /* 省略 Partition 和 Distribution 信息 */ ;
即 Uniq 模型完全可以用聚合模型中的 REPLACE 方式替代。其內(nèi)部的實(shí)現(xiàn)方式和數(shù)據(jù)存儲(chǔ)方式也完全一樣。這里不再繼續(xù)舉例說明。
Duplicate 模型(冗余模型)
在某些多維分析場(chǎng)景下,數(shù)據(jù)既沒有主鍵,也沒有聚合需求。因此,我們引入 Duplicate 數(shù)據(jù)模型來滿足這類需求。舉例說明。
ColumnName | Type | SortKey | Comment |
---|---|---|---|
timestamp | DATETIME | Yes | 日志時(shí)間 |
type | INT | Yes | 日志類型 |
error_code | INT | Yes | 錯(cuò)誤碼 |
error_msg | VARCHAR(1024) | No | 錯(cuò)誤詳細(xì)信息 |
op_id | BIGINT | No | 負(fù)責(zé)人id |
op_time | DATETIME | No | 處理時(shí)間 |
建表語句如下:
CREATE TABLE IF NOT EXISTS example_db.expamle_tbl ( `timestamp` DATETIME NOT NULL COMMENT "日志時(shí)間", `type` INT NOT NULL COMMENT "日志類型", `error_code` INT COMMENT "錯(cuò)誤碼", `error_msg` VARCHAR(1024) COMMENT "錯(cuò)誤詳細(xì)信息", `op_id` BIGINT COMMENT "負(fù)責(zé)人id", `op_time` DATETIME COMMENT "處理時(shí)間" ) DUPLICATE KEY(`timestamp`, `type`) ... /* 省略 Partition 和 Distribution 信息 */ ;
這種數(shù)據(jù)模型區(qū)別于 Aggregate 和 Uniq 模型。數(shù)據(jù)完全按照導(dǎo)入文件中的數(shù)據(jù)進(jìn)行存儲(chǔ),不會(huì)有任何聚合。即使兩行數(shù)據(jù)完全相同,也都會(huì)保留。 而在建表語句中指定的 DUPLICATE KEY,只是用來指明底層數(shù)據(jù)按照那些列進(jìn)行排序。(更貼切的名稱應(yīng)該為 “Sorted Column”,這里取名 “DUPLICATE KEY” 只是用以明確表示所用的數(shù)據(jù)模型。關(guān)于 “Sorted Column”的更多解釋,可以參閱前綴索引小節(jié))。在 DUPLICATE KEY 的選擇上,我們建議適當(dāng)?shù)倪x擇前 2-4 列就可以。
這種數(shù)據(jù)模型適用于既沒有聚合需求,又沒有主鍵唯一性約束的原始數(shù)據(jù)的存儲(chǔ)。更多使用場(chǎng)景,可參閱聚合模型的局限性小節(jié)。
ROLLUP
ROLLUP 在多維分析中是“上卷”的意思,即將數(shù)據(jù)按某種指定的粒度進(jìn)行進(jìn)一步聚合。
在 Doris 中,我們將用戶通過建表語句創(chuàng)建出來的表成為 Base 表(Base Table)。Base 表中保存著按用戶建表語句指定的方式存儲(chǔ)的基礎(chǔ)數(shù)據(jù)。
在 Base 表之上,我們可以創(chuàng)建任意多個(gè) ROLLUP 表。這些 ROLLUP 的數(shù)據(jù)是基于 Base 表產(chǎn)生的,并且在物理上是獨(dú)立存儲(chǔ)的。
ROLLUP 表的基本作用,在于在 Base 表的基礎(chǔ)上,獲得更粗粒度的聚合數(shù)據(jù)。
下面我們用示例詳細(xì)說明在不同數(shù)據(jù)模型中的 ROLLUP 表及其作用。
Aggregate 和 Uniq 模型中的 ROLLUP
因?yàn)?Uniq 只是 Aggregate 模型的一個(gè)特例,所以這里我們不加以區(qū)別。
示例1:獲得每個(gè)用戶的總消費(fèi)
接Aggregate 模型小節(jié)的示例2,Base 表結(jié)構(gòu)如下:
ColumnName | Type | AggregationType | Comment |
---|---|---|---|
user_id | LARGEINT | 用戶id | |
date | DATE | 數(shù)據(jù)灌入日期 | |
timestamp | DATETIME | 數(shù)據(jù)灌入時(shí)間,精確到秒 | |
city | VARCHAR(20) | 用戶所在城市 | |
age | SMALLINT | 用戶年齡 | |
sex | TINYINT | 用戶性別 | |
last_visit_date | DATETIME | REPLACE | 用戶最后一次訪問時(shí)間 |
cost | BIGINT | SUM | 用戶總消費(fèi) |
max_dwell_time | INT | MAX | 用戶最大停留時(shí)間 |
min_dwell_time | INT | MIN | 用戶最小停留時(shí)間 |
存儲(chǔ)的數(shù)據(jù)如下:
user_id | date | timestamp | city | age | sex | last_visit_date | cost | max_dwell_time | min_dwell_time |
---|---|---|---|---|---|---|---|---|---|
10000 | 2017-10-01 | 2017-10-01 08:00:05 | 北京 | 20 | 0 | 2017-10-01 06:00:00 | 20 | 10 | 10 |
10000 | 2017-10-01 | 2017-10-01 09:00:05 | 北京 | 20 | 0 | 2017-10-01 07:00:00 | 15 | 2 | 2 |
10001 | 2017-10-01 | 2017-10-01 18:12:10 | 北京 | 30 | 1 | 2017-10-01 17:05:45 | 2 | 22 | 22 |
10002 | 2017-10-02 | 2017-10-02 13:10:00 | 上海 | 20 | 1 | 2017-10-02 12:59:12 | 200 | 5 | 5 |
10003 | 2017-10-02 | 2017-10-02 13:15:00 | 廣州 | 32 | 0 | 2017-10-02 11:20:00 | 30 | 11 | 11 |
10004 | 2017-10-01 | 2017-10-01 12:12:48 | 深圳 | 35 | 0 | 2017-10-01 10:00:15 | 100 | 3 | 3 |
10004 | 2017-10-03 | 2017-10-03 12:38:20 | 深圳 | 35 | 0 | 2017-10-03 10:20:22 | 11 | 6 | 6 |
在此基礎(chǔ)上,我們創(chuàng)建一個(gè) ROLLUP:
ColumnName |
---|
user_id |
cost |
該 ROLLUP 只包含兩列:user_id 和 cost。則創(chuàng)建完成后,該 ROLLUP 中存儲(chǔ)的數(shù)據(jù)如下:
user_id | cost |
---|---|
10000 | 35 |
10001 | 2 |
10002 | 200 |
10003 | 30 |
10004 | 111 |
可以看到,ROLLUP 中僅保留了每個(gè) user_id,在 cost 列上的 SUM 的結(jié)果。那么當(dāng)我們進(jìn)行如下查詢時(shí):
SELECT user_id, sum(cost) FROM table GROUP BY user_id;
Doris 會(huì)自動(dòng)命中這個(gè) ROLLUP 表,從而只需掃描極少的數(shù)據(jù)量,即可完成這次聚合查詢。
示例2:獲得不同城市,不同年齡段用戶的總消費(fèi)、最長(zhǎng)和最短頁面駐留時(shí)間
緊接示例1。我們?cè)?Base 表基礎(chǔ)之上,再創(chuàng)建一個(gè) ROLLUP:
ColumnName | Type | AggregationType | Comment |
---|---|---|---|
city | VARCHAR(20) | 用戶所在城市 | |
age | SMALLINT | 用戶年齡 | |
cost | BIGINT | SUM | 用戶總消費(fèi) |
max_dwell_time | INT | MAX | 用戶最大停留時(shí)間 |
min_dwell_time | INT | MIN | 用戶最小停留時(shí)間 |
則創(chuàng)建完成后,該 ROLLUP 中存儲(chǔ)的數(shù)據(jù)如下:
city | age | cost | max_dwell_time | min_dwell_time |
---|---|---|---|---|
北京 | 20 | 0 | 30 | 10 |
北京 | 30 | 1 | 2 | 22 |
上海 | 20 | 1 | 200 | 5 |
廣州 | 32 | 0 | 30 | 11 |
深圳 | 35 | 0 | 111 | 6 |
當(dāng)我們進(jìn)行如下這些查詢時(shí):
Doris 會(huì)自動(dòng)命中這個(gè) ROLLUP 表。
SELECT city, age, sum(cost), max(max_dwell_time), min(min_dwell_time) FROM table GROUP BY city, age; SELECT city, sum(cost), max(max_dwell_time), min(min_dwell_time) FROM table GROUP BY city; SELECT city, age, sum(cost), min(min_dwell_time) FROM table GROUP BY city, age;
Duplicate 模型中的 ROLLUP
因?yàn)?Duplicate 模型沒有聚合的語意。所以該模型中的 ROLLUP,已經(jīng)失去了“上卷”這一層含義。而僅僅是作為調(diào)整列順序,以命中前綴索引的作用。我們將在接下來的小節(jié)中,詳細(xì)介紹前綴索引,以及如何使用ROLLUP改變前綴索引,以獲得更好的查詢效率。
前綴索引與 ROLLUP
前綴索引
不同于傳統(tǒng)的數(shù)據(jù)庫(kù)設(shè)計(jì),Doris 不支持在任意列上創(chuàng)建索引。Doris 這類 MPP 架構(gòu)的 OLAP 數(shù)據(jù)庫(kù),通常都是通過提高并發(fā),來處理大量數(shù)據(jù)的。
本質(zhì)上,Doris 的數(shù)據(jù)存儲(chǔ)在類似 SSTable(Sorted String Table)的數(shù)據(jù)結(jié)構(gòu)中。該結(jié)構(gòu)是一種有序的數(shù)據(jù)結(jié)構(gòu),可以按照指定的列進(jìn)行排序存儲(chǔ)。在這種數(shù)據(jù)結(jié)構(gòu)上,以排序列作為條件進(jìn)行查找,會(huì)非常的高效。
在 Aggregate、Uniq 和 Duplicate 三種數(shù)據(jù)模型中。底層的數(shù)據(jù)存儲(chǔ),是按照各自建表語句中,AGGREGATE KEY、UNIQ KEY 和 DUPLICATE KEY 中指定的列進(jìn)行排序存儲(chǔ)的。
而前綴索引,即在排序的基礎(chǔ)上,實(shí)現(xiàn)的一種根據(jù)給定前綴列,快速查詢數(shù)據(jù)的索引方式。
我們將一行數(shù)據(jù)的前 36 個(gè)字節(jié) 作為這行數(shù)據(jù)的前綴索引。當(dāng)遇到 VARCHAR 類型時(shí),前綴索引會(huì)直接截?cái)?。我們舉例說明:
以下表結(jié)構(gòu)的前綴索引為 user_id(8Byte) + age(8Bytes) + message(prefix 20 Bytes)。
ColumnName | Type |
---|---|
user_id | BIGINT |
age | INT |
message | VARCHAR(100) |
max_dwell_time | DATETIME |
min_dwell_time | DATETIME |
以下表結(jié)構(gòu)的前綴索引為 user_name(20 Bytes)。即使沒有達(dá)到 36 個(gè)字節(jié),因?yàn)橛龅?VARCHAR,所以直接截?cái)?,不再往后繼續(xù)。
ColumnName | Type |
---|---|
user_name | VARCHAR(20) |
age | INT |
message | VARCHAR(100) |
max_dwell_time | DATETIME |
min_dwell_time | DATETIME |
當(dāng)我們的查詢條件,是前綴索引的前綴時(shí),可以極大的加快查詢速度。比如在第一個(gè)例子中,我們執(zhí)行如下查詢:
SELECT * FROM table WHERE user_id=1829239 and age=20;
該查詢的效率會(huì)遠(yuǎn)高于如下查詢:
SELECT * FROM table WHERE age=20;
所以在建表時(shí),正確的選擇列順序,能夠極大地提高查詢效率。
ROLLUP 調(diào)整前綴索引
因?yàn)榻ū頃r(shí)已經(jīng)指定了列順序,所以一個(gè)表只有一種前綴索引。這對(duì)于使用其他不能命中前綴索引的列作為條件進(jìn)行的查詢來說,效率上可能無法滿足需求。因此,我們可以通過創(chuàng)建 ROLLUP 來人為的調(diào)整列順序。舉例說明。
Base 表結(jié)構(gòu)如下:
ColumnName | Type |
---|---|
user_id | BIGINT |
age | INT |
message | VARCHAR(100) |
max_dwell_time | DATETIME |
min_dwell_time | DATETIME |
我們可以在此基礎(chǔ)上創(chuàng)建一個(gè) ROLLUP 表:
ColumnName | Type |
---|---|
age | INT |
user_id | BIGINT |
message | VARCHAR(100) |
max_dwell_time | DATETIME |
min_dwell_time | DATETIME |
可以看到,ROLLUP 和 Base 表的列完全一樣,只是將 user_id 和 age 的順序調(diào)換了。那么當(dāng)我們進(jìn)行如下查詢時(shí):
SELECT * FROM table where age=20 and massage LIKE "%error%";
會(huì)優(yōu)先選擇 ROLLUP 表,因?yàn)?ROLLUP 的前綴索引匹配度更高。
ROLLUP 的幾點(diǎn)說明
- ROLLUP 最根本的作用是提高某些查詢的查詢效率(無論是通過聚合來減少數(shù)據(jù)量,還是修改列順序以匹配前綴索引)。因此 ROLLUP 的含義已經(jīng)超出了 “上卷” 的范圍。這也是為什么我們?cè)谠创a中,將其命名為 Materized Index(物化索引)的原因。
- ROLLUP 是附屬于 Base 表的,可以看做是 Base 表的一種輔助數(shù)據(jù)結(jié)構(gòu)。用戶可以在 Base 表的基礎(chǔ)上,創(chuàng)建或刪除 ROLLUP,但是不能在查詢中顯式的指定查詢某 ROLLUP。是否命中 ROLLUP 完全由 Doris 系統(tǒng)自動(dòng)決定。
- ROLLUP 的數(shù)據(jù)是獨(dú)立物理存儲(chǔ)的。因此,創(chuàng)建的 ROLLUP 越多,占用的磁盤空間也就越大。同時(shí)對(duì)導(dǎo)入速度也會(huì)有影響(導(dǎo)入的ETL階段會(huì)自動(dòng)產(chǎn)生所有 ROLLUP 的數(shù)據(jù)),但是不會(huì)降低查詢效率(只會(huì)更好)。
- ROLLUP 的數(shù)據(jù)更新與 Base 表示完全同步的。用戶無需關(guān)心這個(gè)問題。
- ROLLUP 中列的聚合方式,與 Base 表完全相同。在創(chuàng)建 ROLLUP 無需指定,也不能修改。
- 查詢能否命中 ROLLUP 的一個(gè)必要條件(非充分條件)是,查詢所涉及的所有列(包括 select list 和 where 中的查詢條件列等)都存在于該 ROLLUP 的列中。否則,查詢只能命中 Base 表。
- 某些類型的查詢(如 count(*))在任何條件下,都無法命中 ROLLUP。具體參見接下來的 聚合模型的局限性 一節(jié)。
- 可以通過
EXPLAIN your_sql;
命令獲得查詢執(zhí)行計(jì)劃,在執(zhí)行計(jì)劃中,查看是否命中 ROLLUP。 - 可以通過
DESC tbl_name ALL;
語句顯示 Base 表和所有已創(chuàng)建完成的 ROLLUP。
以下文檔有一些對(duì)這里Rollup說明的補(bǔ)充Rollup
聚合模型的局限性
這里我們針對(duì) Aggregate 模型(包括 Uniq 模型),來介紹下聚合模型的局限性。
在聚合模型中,模型對(duì)外展現(xiàn)的,是最終聚合后的數(shù)據(jù)。也就是說,任何還未聚合的數(shù)據(jù)(比如說兩個(gè)不同導(dǎo)入批次的數(shù)據(jù)),必須通過某種方式,以保證對(duì)外展示的一致性。我們舉例說明。
假設(shè)表結(jié)構(gòu)如下:
ColumnName | Type | AggregationType | Comment |
---|---|---|---|
user_id | LARGEINT | 用戶id | |
date | DATE | 數(shù)據(jù)灌入日期 | |
cost | BIGINT | SUM | 用戶總消費(fèi) |
假設(shè)存儲(chǔ)引擎中有如下兩個(gè)已經(jīng)導(dǎo)入完成的批次的數(shù)據(jù):
batch 1
user_id | date | cost |
---|---|---|
10001 | 2017-11-20 | 50 |
10002 | 2017-11-21 | 39 |
batch 2
user_id | date | cost |
---|---|---|
10001 | 2017-11-20 | 1 |
10001 | 2017-11-21 | 5 |
10003 | 2017-11-22 | 22 |
可以看到,用戶 10001 分屬在兩個(gè)導(dǎo)入批次中的數(shù)據(jù)還沒有聚合。但是為了保證用戶只能查詢到如下最終聚合后的數(shù)據(jù):
user_id | date | cost |
---|---|---|
10001 | 2017-11-20 | 51 |
10001 | 2017-11-21 | 5 |
10002 | 2017-11-21 | 39 |
10003 | 2017-11-22 | 22 |
我們?cè)诓樵円嬷屑尤肓司酆纤阕?,來保證數(shù)據(jù)對(duì)外的一致性。
另外,在聚合列(Value)上,執(zhí)行與聚合類型不一致的聚合類查詢時(shí),要注意語意。比如我們?cè)谌缟鲜纠袌?zhí)行如下查詢:
SELECT MIN(cost) FROM table;
得到的結(jié)果是 5,而不是 1。
同時(shí),這種一致性保證,在某些查詢中,會(huì)極大的降低查詢效率。
我們以最基本的 count(*) 查詢?yōu)槔?/p>
SELECT COUNT(*) FROM table;
在其他數(shù)據(jù)庫(kù)中,這類查詢都會(huì)很快的返回結(jié)果。因?yàn)樵趯?shí)現(xiàn)上,我們可以通過如“導(dǎo)入時(shí)對(duì)行進(jìn)行計(jì)數(shù),保存count的統(tǒng)計(jì)信息”,或者在查詢時(shí)“僅掃描某一列數(shù)據(jù),獲得count值”的方式,只需很小的開銷,即可獲得查詢結(jié)果。但是在 Doris 的聚合模型中,這種查詢的開銷非常大。
我們以剛才的數(shù)據(jù)為例:
batch 1
user_id | date | cost |
---|---|---|
10001 | 2017-11-20 | 50 |
10002 | 2017-11-21 | 39 |
batch 2
user_id | date | cost |
---|---|---|
10001 | 2017-11-20 | 1 |
10001 | 2017-11-21 | 5 |
10003 | 2017-11-22 | 22 |
因?yàn)樽罱K的聚合結(jié)果為:
user_id | date | cost |
---|---|---|
10001 | 2017-11-20 | 51 |
10001 | 2017-11-21 | 5 |
10002 | 2017-11-21 | 39 |
10003 | 2017-11-22 | 22 |
所以,select count(*) from table;
的正確結(jié)果應(yīng)該為 4。但如果我們只掃描 user_id
這一列,如果加上查詢時(shí)聚合,最終得到的結(jié)果是 3(10001, 10002, 10003)。而如果不加查詢時(shí)聚合,則得到的結(jié)果是 5(兩批次一共5行數(shù)據(jù))??梢娺@兩個(gè)結(jié)果都是不對(duì)的。
為了得到正確的結(jié)果,我們必須同時(shí)讀取 user_id
和 date
這兩列的數(shù)據(jù),再加上查詢時(shí)聚合,才能返回 4 這個(gè)正確的結(jié)果。也就是說,在 count(*) 查詢中,Doris 必須掃描所有的 AGGREGATE KEY 列(這里就是 user_id
和 date
),并且聚合后,才能得到語意正確的結(jié)果。當(dāng)聚合列非常多時(shí),count(*) 查詢需要掃描大量的數(shù)據(jù)。
因此,當(dāng)業(yè)務(wù)上有頻繁的 count(*) 查詢時(shí),我們建議用戶通過增加一個(gè)值衡為 1 的,聚合類型為 SUM 的列來模擬 count(*)。如剛才的例子中的表結(jié)構(gòu),我們修改如下:
ColumnName | Type | AggreateType | Comment |
---|---|---|---|
user_id | BIGINT | 用戶id | |
date | DATE | 數(shù)據(jù)灌入日期 | |
cost | BIGINT | SUM | 用戶總消費(fèi) |
count | BIGINT | SUM | 用于計(jì)算count |
增加一個(gè) count 列,并且導(dǎo)入數(shù)據(jù)中,該列值衡為 1。則 select count(*) from table;
的結(jié)果等價(jià)于 select sum(count) from table;
。而后者的查詢效率將遠(yuǎn)高于前者。不過這種方式也有使用限制,就是用戶需要自行保證,不會(huì)重復(fù)導(dǎo)入 AGGREGATE KEY 列都相同的行。否則,select sum(count) from table;
只能表述原始導(dǎo)入的行數(shù),而不是 select count(*) from table;
的語義。
另一種方式,就是 將如上的 count
列的聚合類型改為 REPLACE,且依然值衡為 1。那么 select sum(count) from table;
和 select count(*) from table;
的結(jié)果將是一致的。并且這種方式,沒有導(dǎo)入重復(fù)行的限制。
Duplicate 模型
Duplicate 模型沒有聚合模型的這個(gè)局限性。因?yàn)樵撃P筒簧婕熬酆险Z意,在做 count(*) 查詢時(shí),任意選擇一列查詢,即可得到語意正確的結(jié)果。
數(shù)據(jù)模型的選擇建議
因?yàn)閿?shù)據(jù)模型在建表時(shí)就已經(jīng)確定,且無法修改。所以,選擇一個(gè)合適的數(shù)據(jù)模型非常重要。
- Aggregate 模型可以通過預(yù)聚合,極大地降低聚合查詢時(shí)所需掃描的數(shù)據(jù)量和查詢的計(jì)算量,非常適合有固定模式的報(bào)表類查詢場(chǎng)景。但是該模型對(duì) count(*) 查詢很不友好。同時(shí)因?yàn)楣潭?Value 列上的聚合方式,在進(jìn)行其他類型的聚合查詢時(shí),需要考慮語意正確性。
- Uniq 模型針對(duì)需要唯一主鍵約束的場(chǎng)景,可以保證主鍵唯一性約束。但是無法利用 ROLLUP 等預(yù)聚合帶來的查詢優(yōu)勢(shì)(因?yàn)楸举|(zhì)是 REPLACE,沒有 SUM 這種聚合方式)。
- Duplicate 適合任意維度的 Ad-hoc 查詢。雖然同樣無法利用預(yù)聚合的特性,但是不受聚合模型的約束,可以發(fā)揮列存模型的優(yōu)勢(shì)(只讀取相關(guān)列,而不需要讀取所有 Key 列)。
以上就是Doris 數(shù)據(jù)模型ROLLUP及前綴索引官方教程的詳細(xì)內(nèi)容,更多關(guān)于Doris 數(shù)據(jù)模型的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
在SQL SERVER中查詢數(shù)據(jù)庫(kù)中第幾條至第幾條之間的數(shù)據(jù)SQL語句寫法
這篇文章主要介紹了在SQL SERVER中查詢數(shù)據(jù)庫(kù)中第幾條至第幾條之間的數(shù)據(jù)SQL語句寫法,需要的朋友可以參考下2015-11-11深入理解數(shù)據(jù)庫(kù)之表的唯一、自增等七大約束
真正約束字段的是數(shù)據(jù)類型,但是數(shù)據(jù)類型約束很單一,需要有一些額外的約束,更好的保證數(shù)據(jù)的合法性,從業(yè)務(wù)邏輯角度保證數(shù)據(jù)的正確性,本文就來介紹一下數(shù)據(jù)庫(kù)之表的唯一、自增等七大約束,感興趣的可以了解一下2023-09-09樹形結(jié)構(gòu)數(shù)據(jù)庫(kù)表Schema設(shè)計(jì)的兩種方案
程序設(shè)計(jì)過程中,我們常常用樹形結(jié)構(gòu)來表征某些數(shù)據(jù)的關(guān)聯(lián)關(guān)系,如企業(yè)上下級(jí)部門、欄目結(jié)構(gòu)、商品分類等等,下面這篇文章主要給大家介紹了關(guān)于樹形結(jié)構(gòu)數(shù)據(jù)庫(kù)表Schema設(shè)計(jì)的兩種方案,需要的朋友可以參考下2021-09-09達(dá)夢(mèng)數(shù)據(jù)庫(kù)(DM數(shù)據(jù)庫(kù))的查詢與操作指南
達(dá)夢(mèng)數(shù)據(jù)庫(kù)是一款具有自主知識(shí)產(chǎn)權(quán)的高性能數(shù)據(jù)庫(kù)管理系統(tǒng),這篇文章詳細(xì)介紹了其基本操作、數(shù)據(jù)表操作、數(shù)據(jù)查詢操作及其他常用操作,幫助讀者更好地掌握和使用這款數(shù)據(jù)庫(kù),需要的朋友可以參考下2025-02-02解決Navicat數(shù)據(jù)庫(kù)連接成功但密碼忘記的問題
這篇文章給大家介紹了Navicat數(shù)據(jù)庫(kù)連接成功,密碼忘記如何解決,文中給大家介紹了兩種解決方法,有詳細(xì)的圖文講解,需要的朋友可以參考下2023-08-08