Apache?Doris基礎(chǔ)簡(jiǎn)介
Apache Doris 是一個(gè)現(xiàn)代化的 MPP(Massively Parallel Processing,即大規(guī)模并行處理)分析型數(shù)據(jù)庫(kù)產(chǎn)品。僅需亞秒級(jí)響應(yīng)時(shí)間即可獲得查詢結(jié)果,有效地支持實(shí)時(shí)數(shù)據(jù)分析。
一、Doris架構(gòu)
Doris 的架構(gòu)很簡(jiǎn)潔,只設(shè) FE(Frontend)、BE(Backend)兩種角色、兩個(gè)進(jìn)程,不依賴于外部組件,方便部署和運(yùn)維,F(xiàn)E、BE 都可線性擴(kuò)展。
- FE(Frontend):存儲(chǔ)、維護(hù)集群元數(shù)據(jù);負(fù)責(zé)接收、解析查詢請(qǐng)求,規(guī)劃查詢計(jì)劃,調(diào)度查詢執(zhí)行,返回查詢結(jié)果。主要有三個(gè)角色:
- Leader 和 Follower:主要是用來(lái)達(dá)到元數(shù)據(jù)的高可用,保證單節(jié)點(diǎn)宕機(jī)的情況下,元數(shù)據(jù)能夠?qū)崟r(shí)地在線恢復(fù),而不影響整個(gè)服務(wù)。
- Observer:用來(lái)擴(kuò)展查詢節(jié)點(diǎn),同時(shí)起到元數(shù)據(jù)備份的作用。如果在發(fā)現(xiàn)集群壓力非常大的情況下,需要去擴(kuò)展整個(gè)查詢的能力,那么可以加 observer 的節(jié)點(diǎn)。observer 不參與任何的寫(xiě)入,只參與讀取。
- BE(Backend):負(fù)責(zé)物理數(shù)據(jù)的存儲(chǔ)和計(jì)算;依據(jù) FE 生成的物理計(jì)劃,分布式執(zhí)行查詢。
- 數(shù)據(jù)的可靠性由 BE 保證,BE 會(huì)對(duì)整個(gè)數(shù)據(jù)存儲(chǔ)多副本或者是三副本。副本數(shù)可根據(jù)需求動(dòng)態(tài)調(diào)整。
- MySQL Client:Doris 借助 MySQL 協(xié)議,用戶使用任意 MySQL 的 ODBC/JDBC 以及 MySQL 的客戶端,都可以直接訪問(wèn) Doris。
- Broker:Broker 為一個(gè)獨(dú)立的無(wú)狀態(tài)進(jìn)程。封裝了文件系統(tǒng)接口,提供 Doris 讀取遠(yuǎn)端存儲(chǔ)系統(tǒng)中文件的能力,包括 HDFS,S3,BOS 等。
FE負(fù)責(zé)存儲(chǔ)元數(shù)據(jù)和接收前端請(qǐng)求,BE負(fù)責(zé)執(zhí)行查詢,Observer負(fù)責(zé)擴(kuò)展查詢;
此處的Doris只是引用了Mysql的客戶端協(xié)議,方便用戶可以通過(guò)MysqlClient直接訪問(wèn)Doris,類(lèi)似的mysql -user -password -P 9030就能通過(guò)像登錄mysql客戶端的方式訪問(wèn)Doris。
二、基本概念
1. Row & Column
一張表包括行(Row)和列(Column)。Row 即用戶的一行數(shù)據(jù)。Column 用于描述一行數(shù)據(jù)中不同的字段。
- 在默認(rèn)的數(shù)據(jù)模型中,Column 只分為排序列和非排序列。存儲(chǔ)引擎會(huì)按照排序列對(duì)數(shù)據(jù)進(jìn)行排序存儲(chǔ),并建立稀疏索引,以便在排序數(shù)據(jù)上進(jìn)行快速查找。
- 在聚合模型中,Column 可以分為兩大類(lèi):Key 和 Value。從業(yè)務(wù)角度看,Key 和Value 可以分別對(duì)應(yīng)維度列和指標(biāo)列。
從聚合模型的角度來(lái)說(shuō),Key 列相同的行,會(huì)聚合成一行。其中 Value 列的聚合方式由用戶在表時(shí)指定。
2. Partition & Tablet
在 Doris 的存儲(chǔ)引擎中,用戶數(shù)據(jù)首先被劃分成若干個(gè)分區(qū)(Partition),劃分的規(guī)則通常是按照用戶指定的分區(qū)列進(jìn)行范圍劃分,比如按時(shí)間劃分。而在每個(gè)分區(qū)內(nèi),數(shù)據(jù)被進(jìn)一步的按照 Hash 的方式分桶,分桶的規(guī)則是要找用戶指定的分桶列的值進(jìn)行 Hash 后分桶。每個(gè)分桶就是一個(gè)數(shù)據(jù)分片(Tablet),也是數(shù)據(jù)劃分的最小邏輯單元。
- Tablet 之間的數(shù)據(jù)是沒(méi)有交集的,獨(dú)立存儲(chǔ)的。Tablet 也是數(shù)據(jù)移動(dòng)、復(fù)制等操作的最小物理存儲(chǔ)單元。
- Partition 可以視為是邏輯上最小的管理單元。數(shù)據(jù)的導(dǎo)入與刪除,都可以或僅能針對(duì)一個(gè) Partition 進(jìn)行。
此處的Partition & Tablet類(lèi)似Hive中的分區(qū)分桶。
3. 建表示例
建表語(yǔ)法:
CREATE [EXTERNAL] TABLE [IF NOT EXISTS] [database.]table_name ( column_definition1[, column_definition2, ...] [, index_definition1[, index_definition12,]] ) [ENGINE = [olap|mysql|broker|hive]] [key_desc] [COMMENT "table comment"]; [partition_desc] [distribution_desc] [rollup_index] [PROPERTIES ("key"="value", ...)] [BROKER PROPERTIES ("key"="value", ...)];
Doris 的建表是一個(gè)同步命令,命令返回成功,即表示建表成功。
Doris 支持支持單分區(qū)和復(fù)合分區(qū)兩種建表方式。
- 復(fù)合分區(qū):既有分區(qū)也有分桶
- 第一級(jí)稱為 Partition,即分區(qū)。用戶可以指定某一維度列作為分區(qū)列(當(dāng)前只支持整型和時(shí)間類(lèi)型的列),并指定每個(gè)分區(qū)的取值范圍。
- 第二級(jí)稱為 Distribution,即分桶。用戶可以指定一個(gè)或多個(gè)維度列以及桶數(shù)對(duì)數(shù)據(jù)進(jìn)行 HASH 分布。
- 單分區(qū):只做 HASH 分布,即只分桶。
字段類(lèi)型
TINYINT | 1 字節(jié) | 范圍:-2^7 + 1 ~ 2^7 - 1 |
SMALLINT | 2 字節(jié) | 范圍:-2^15 + 1 ~ 2^15 - 1 |
INT | 4 字節(jié) | 范圍:-2^31 + 1 ~ 2^31 - 1 |
BIGINT | 8 字節(jié) | 范圍:-2^63 + 1 ~ 2^63 - 1 |
LARGEINT | 16 字節(jié) | 范圍:-2^127 + 1 ~ 2^127 - 1 |
FLOAT | 4 字節(jié) | 支持科學(xué)計(jì)數(shù)法 |
DOUBLE | 12 字節(jié) | 支持科學(xué)計(jì)數(shù)法 |
DECIMAL[(precision, scale)] | 16 字節(jié) | 保證精度的小數(shù)類(lèi)型。 默認(rèn)是DECIMAL(10, 0) precision: 1 ~ 27 scale: 0 ~ 9其中整數(shù)部分為 1 ~ 18不支持科學(xué)計(jì)數(shù)法 |
DATE | 3 字節(jié) | 范圍:0000-01-01 ~ 9999-12-31 |
DATETIME | 8 字節(jié) | 范圍:0000-01-01 00:00:00 ~ 9999-12-31 23:59:59 |
CHAR[(length)] | 定長(zhǎng)字符串。長(zhǎng)度范圍:1 ~ 255。默認(rèn)為 1 | |
VARCHAR[(length)] | 變長(zhǎng)字符串。長(zhǎng)度范圍:1 ~ 65533 | |
BOOLEAN | 與 TINYINT 一樣,0 代表 false,1 代表 true | |
HLL | 1~16385 個(gè)字節(jié) | hll 列類(lèi)型,不需要指定長(zhǎng)度和默認(rèn)值、長(zhǎng)度根據(jù)數(shù)據(jù)的聚合程度系統(tǒng)內(nèi)控制,并且 HLL 列只能通過(guò) 配 套 的 hll_union_agg 、Hll_cardinality、hll_hash 進(jìn)行查詢或使用 |
BITMAP | bitmap 列類(lèi)型,不需要指定長(zhǎng)度和默認(rèn)值。表示整型的集合,元素最大支持到 2^64 - 1 | |
STRING | 變長(zhǎng)字符串,0.15 版本支持,最大支持 2147483643 字節(jié)(2GB-4),長(zhǎng)度還受 be 配置string_type_soft_limit , 實(shí)際能存儲(chǔ)的最大長(zhǎng)度取兩者最小值。只能用在 value 列,不能用在 key 列和分區(qū)、分桶列 |
注:聚合模型在定義字段類(lèi)型后,可以指定字段的 agg_type 聚合類(lèi)型,如果不指定,則該列為 key 列。否則,該列為 value 列, 類(lèi)型包括:SUM、MAX、MIN、REPLACE。
Range Partition
CREATE TABLE IF NOT EXISTS example_db.expamle_range_tbl ( `user_id` LARGEINT NOT NULL COMMENT "用戶 id", `date` DATE NOT NULL COMMENT "數(shù)據(jù)灌入日期時(shí)間", `timestamp` DATETIME 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 "用戶最后一次訪問(wèn)時(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í)間" ) ENGINE=olap AGGREGATE KEY(`user_id`, `date`, `timestamp`, `city`, `age`, `sex`) PARTITION BY RANGE(`date`) ( PARTITION `p201701` VALUES LESS THAN ("2017-02-01"), PARTITION `p201702` VALUES LESS THAN ("2017-03-01"), PARTITION `p201703` VALUES LESS THAN ("2017-04-01") ) DISTRIBUTED BY HASH(`user_id`) BUCKETS 16 PROPERTIES ( "replication_num" = "3", "storage_medium" = "SSD", "storage_cooldown_time" = "2018-01-01 12:00:00" );
上述建表語(yǔ)句中,AGGREGATE KEY(user_id
, date
, timestamp
, city
, age
, sex
)屬于是key列,下邊的列在定義類(lèi)型后添加了聚合類(lèi)型(MAX,MIN,REPLACE,SUM)的列稱為value列。
`last_visit_date` DATETIME REPLACE DEFAULT "1970-01-01 00:00:00" COMMENT "用戶最后一次訪問(wèn)時(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í)間"
此處還使用了復(fù)合分區(qū),使用PARTITION BY RANGE(date
)指定使用date字段進(jìn)行范圍分區(qū),分區(qū)的范圍是左閉右開(kāi)的,并通過(guò)HASH算法根據(jù)user_id列進(jìn)行分桶。
PARTITION BY RANGE(`date`) ( PARTITION `p201701` VALUES LESS THAN ("2017-02-01"), PARTITION `p201702` VALUES LESS THAN ("2017-03-01"), PARTITION `p201703` VALUES LESS THAN ("2017-04-01") ) DISTRIBUTED BY HASH(`user_id`) BUCKETS 16
List Partition
CREATE TABLE IF NOT EXISTS example_db.expamle_list_tbl ( `user_id` LARGEINT NOT NULL COMMENT "用戶 id", `date` DATE NOT NULL COMMENT "數(shù)據(jù)灌入日期時(shí)間", `timestamp` DATETIME 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 "用戶最后一次訪問(wèn)時(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í) 間" ) ENGINE=olap AGGREGATE KEY(`user_id`, `date`, `timestamp`, `city`, `age`, `sex`) PARTITION BY LIST(`city`) ( PARTITION `p_cn` VALUES IN ("Beijing", "Shanghai", "Hong Kong"), PARTITION `p_usa` VALUES IN ("New York", "San Francisco"), PARTITION `p_jp` VALUES IN ("Tokyo") ) DISTRIBUTED BY HASH(`user_id`) BUCKETS 16 PROPERTIES ( "replication_num" = "3", "storage_medium" = "SSD", "storage_cooldown_time" = "2018-01-01 12:00:00" );
(1)列的定義
在AGGREGATE KEY 數(shù)據(jù)模型中,所有沒(méi)有指定聚合方式(SUM、REPLACE、MAX、MIN)的列視為 Key 列。而其余則為 Value 列。
定義列時(shí),可參照如下建議:
- Key 列必須在所有 Value 列之前。
- 盡量選擇整型類(lèi)型。因?yàn)檎皖?lèi)型的計(jì)算和查找比較效率遠(yuǎn)高于字符串。
- 對(duì)于不同長(zhǎng)度的整型類(lèi)型的選擇原則,遵循夠用即可。
- 對(duì)于 VARCHAR 和 STRING 類(lèi)型的長(zhǎng)度,遵循 夠用即可。
- 所有列的總字節(jié)長(zhǎng)度(包括 Key 和 Value)不能超過(guò) 100KB。
(2)分區(qū)分桶
Doris 支持兩層的數(shù)據(jù)劃分。第一層是 Partition,支持 Range 和 List 的劃分方式。第二層是 Bucket(Tablet),僅支持 Hash 的劃分方式。
也可以僅使用一層分區(qū)。使用一層分區(qū)時(shí),只支持 Bucket 劃分。
Partition
- Partition 列可以指定一列或多列。分區(qū)類(lèi)必須為 KEY 列。多列分區(qū)的使用方式在后面介紹。
- 不論分區(qū)列是什么類(lèi)型,在寫(xiě)分區(qū)值時(shí),都需要加雙引號(hào)。
- 分區(qū)數(shù)量理論上沒(méi)有上限。
- 當(dāng)不使用 Partition 建表時(shí),系統(tǒng)會(huì)自動(dòng)生成一個(gè)和表名同名的,全值范圍的Partition。該 Partition 對(duì)用戶不可見(jiàn),并且不可刪改。
Range 分區(qū) : 分區(qū)列通常為時(shí)間列,以方便的管理新舊數(shù)據(jù)。不可添加范圍重疊的分區(qū)。
- VALUES LESS THAN (…) 僅指定上界,系統(tǒng)會(huì)將前一個(gè)分區(qū)的上界作為該分區(qū)的下界,生成一個(gè)左閉右開(kāi)的區(qū)間。分區(qū)的刪除不會(huì)改變已存在分區(qū)的范圍。刪除分區(qū)可能出現(xiàn)空洞。
- VALUES […) 指定同時(shí)指定上下界,生成一個(gè)左閉右開(kāi)的區(qū)間。
通過(guò) VALUES […) 同時(shí)指定上下界比較容易理解。這里舉例說(shuō)明,當(dāng)使用 VALUES LESS THAN (…) 語(yǔ)句進(jìn)行分區(qū)的增刪操作時(shí),分區(qū)范圍的變化情況:
(1)如上 expamle_range_tbl 示例,當(dāng)建表完成后,會(huì)自動(dòng)生成如下 3 個(gè)分區(qū):
p201701: [MIN_VALUE, 2017-02-01) p201702: [2017-02-01, 2017-03-01) p201703: [2017-03-01, 2017-04-01)
(2)增加一個(gè)分區(qū) p201705 VALUES LESS THAN (“2017-06-01”),分區(qū)結(jié)果如下:
p201701: [MIN_VALUE, 2017-02-01)
p201702: [2017-02-01, 2017-03-01)
p201703: [2017-03-01, 2017-04-01)
p201705: [2017-04-01, 2017-06-01)
(3)此時(shí)刪除分區(qū) p201703,則分區(qū)結(jié)果如下:
p201701: [MIN_VALUE, 2017-02-01)
p201702: [2017-02-01, 2017-03-01)
p201705: [2017-04-01, 2017-06-01)
注意: p201702 和 p201705 的分區(qū)范圍并沒(méi)有發(fā)生變化,而這兩個(gè)分區(qū)之間,出現(xiàn)了一個(gè)空洞:[2017-03-01, 2017-04-01)。即如果導(dǎo)入的數(shù)據(jù)范圍在這個(gè)空洞范圍內(nèi),是無(wú)法導(dǎo)入的。
List 分區(qū):分區(qū)列支持 BOOLEAN, TINYINT, SMALLINT, INT, BIGINT, LARGEINT, DATE, DATETIME, CHAR, VARCHAR 數(shù)據(jù)類(lèi)型,分區(qū)值為枚舉值。只有當(dāng)數(shù)據(jù)為目標(biāo)分區(qū)枚舉值其中之一時(shí),才可以命中分區(qū)。不可添加范圍重疊的分區(qū)。
Partition 支持通過(guò) VALUES IN (…) 來(lái)指定每個(gè)分區(qū)包含的枚舉值。下面通過(guò)示例說(shuō)明,進(jìn)行分區(qū)的增刪操作時(shí),分區(qū)的變化。
(1)如上 example_list_tbl 示例,當(dāng)建表完成后,會(huì)自動(dòng)生成如下 3 個(gè)分區(qū):
p_cn: ("Beijing", "Shanghai", "Hong Kong") p_usa: ("New York", "San Francisco") p_jp: ("Tokyo")
(2)增加一個(gè)分區(qū) p_uk VALUES IN (“London”),分區(qū)結(jié)果如下:
p_cn: ("Beijing", "Shanghai", "Hong Kong")
p_usa: ("New York", "San Francisco")
p_jp: ("Tokyo")
p_uk: ("London")
(3)刪除分區(qū) p_jp,分區(qū)結(jié)果如下:
p_cn: ("Beijing", "Shanghai", "Hong Kong")
p_usa: ("New York", "San Francisco")
p_uk: ("London")
Bucket
- 如果使用了 Partition,則 DISTRIBUTED … 語(yǔ)句描述的是數(shù)據(jù)在各個(gè)分區(qū)內(nèi)的劃分規(guī)則。如果不使用 Partition,則描述的是對(duì)整個(gè)表的數(shù)據(jù)的劃分規(guī)則。
- 分桶列可以是多列,但必須為 Key 列。分桶列可以和 Partition 列相同或不同。
- 分桶列的選擇,是在 查詢吞吐 和 查詢并發(fā) 之間的一種權(quán)衡:
- 如果選擇多個(gè)分桶列,則數(shù)據(jù)分布更均勻。如果一個(gè)查詢條件不包含所有分桶列的等值條件,那么該查詢會(huì)觸發(fā)所有分桶同時(shí)掃描,這樣查詢的吞吐會(huì)增加,單個(gè)查詢的延遲隨之降低。這個(gè)方式適合大吞吐低并發(fā)的查詢場(chǎng)景。
指定的分桶列越多,分桶的粒度越細(xì)。
比如使用學(xué)科+班級(jí)的key列進(jìn)行分桶,如果查詢時(shí)只指定了班級(jí),那么會(huì)發(fā)生全分區(qū)掃描。‘
- 如果僅選擇一個(gè)或少數(shù)分桶列,則對(duì)應(yīng)的點(diǎn)查詢可以僅觸發(fā)一個(gè)分桶掃描。此時(shí),當(dāng)多個(gè)點(diǎn)查詢并發(fā)時(shí),這些查詢有較大的概率分別觸發(fā)不同的分桶掃描,各個(gè)查詢之間的 IO 影響較?。ㄓ绕洚?dāng)不同桶分布在不同磁盤(pán)上時(shí)),所以這種方式適合高并發(fā)的點(diǎn)查詢場(chǎng)景。
- 分桶的數(shù)量理論上沒(méi)有上限。
復(fù)合分區(qū)使用場(chǎng)景:
(1)有時(shí)間維度或類(lèi)似帶有有序值的維度,可以以這類(lèi)維度列作為分區(qū)列。分區(qū)粒度可以根據(jù)導(dǎo)入頻次、分區(qū)數(shù)據(jù)量等進(jìn)行評(píng)估。
(2)歷史數(shù)據(jù)刪除需求:如有刪除歷史數(shù)據(jù)的需求(比如僅保留最近 N 天的數(shù)據(jù))。使用復(fù)合分區(qū),可以通過(guò)刪除歷史分區(qū)來(lái)達(dá)到目的。也可以通過(guò)在指定分區(qū)內(nèi)發(fā)送 DELETE 語(yǔ)句進(jìn)行數(shù)據(jù)刪除。
(3)解決數(shù)據(jù)傾斜問(wèn)題:每個(gè)分區(qū)可以單獨(dú)指定分桶數(shù)量。如按天分區(qū),當(dāng)每天的數(shù)據(jù)量差異很大時(shí),可以通過(guò)指定分區(qū)的分桶數(shù),合理劃分不同分區(qū)的數(shù)據(jù),分桶列建議選擇區(qū)分度大的列。
(3)多列分區(qū)
Range 分區(qū)
PARTITION BY RANGE(`date`, `id`) ( PARTITION `p201701_1000` VALUES LESS THAN ("2017-02-01", "1000"), PARTITION `p201702_2000` VALUES LESS THAN ("2017-03-01", "2000"), PARTITION `p201703_all` VALUES LESS THAN ("2017-04-01") )
指定 date
(DATE 類(lèi)型) 和 id
(INT 類(lèi)型) 作為分區(qū)列。以上示例最終得到的分區(qū)如下:
p201701_1000: [(MIN_VALUE, MIN_VALUE), ("2017-02-01", "1000") )
p201702_2000: [("2017-02-01", "1000"), ("2017-03-01", "2000") )
p201703_all: [("2017-03-01", "2000"), ("2017-04-01", MIN_VALUE))
注意,最后一個(gè)分區(qū)用戶缺省只指定了 date
列的分區(qū)值,所以 id
列的分區(qū)值會(huì)默認(rèn)填充 MIN_VALUE
。當(dāng)用戶插入數(shù)據(jù)時(shí),分區(qū)列值會(huì)按照順序依次比較,最終得到對(duì)應(yīng)的分區(qū)。舉例如下:
數(shù)據(jù) --> 分區(qū)
2017-01-01, 200 --> p201701_1000
2017-01-01, 2000 --> p201701_1000
2017-02-01, 100 --> p201701_1000
2017-02-01, 2000 --> p201702_2000
2017-02-15, 5000 --> p201702_2000
2017-03-01, 2000 --> p201703_all
2017-03-10, 1 --> p201703_all
2017-04-01, 1000 --> 無(wú)法導(dǎo)入
2017-05-01, 1000 --> 無(wú)法導(dǎo)入
List 分區(qū)
PARTITION BY LIST(`id`, `city`) ( PARTITION `p1_city` VALUES IN (("1", "Beijing"), ("1", "Shanghai")), PARTITION `p2_city` VALUES IN (("2", "Beijing"), ("2", "Shanghai")), PARTITION `p3_city` VALUES IN (("3", "Beijing"), ("3", "Shanghai")) )
指定 id
(INT 類(lèi)型) 和 city
(VARCHAR 類(lèi)型) 作為分區(qū)列。最終得到的分區(qū)如下:
p1_city: [("1", "Beijing"), ("1", "Shanghai")] p2_city: [("2", "Beijing"), ("2", "Shanghai")] p3_city: [("3", "Beijing"), ("3", "Shanghai")]
當(dāng)用戶插入數(shù)據(jù)時(shí),分區(qū)列值會(huì)按照順序依次比較,最終得到對(duì)應(yīng)的分區(qū)。舉例如下:
數(shù)據(jù) ---> 分區(qū) 1, Beijing ---> p1_city 1, Shanghai ---> p1_city 2, Shanghai ---> p2_city 3, Beijing ---> p3_city 1, Tianjin ---> 無(wú)法導(dǎo)入 4, Beijing ---> 無(wú)法導(dǎo)入
(4)PROPERTIES
- replication_num
- 每個(gè) Tablet 的副本數(shù)量。默認(rèn)為 3,建議保持默認(rèn)即可。在建表語(yǔ)句中,所有 Partition 中的 Tablet 副本數(shù)量統(tǒng)一指定。而在增加新分區(qū)時(shí),可以單獨(dú)指定新分區(qū)中 Tablet 的副本數(shù)量。
- 副本數(shù)量可以在運(yùn)行時(shí)修改。強(qiáng)烈建議保持奇數(shù)。
- 最大副本數(shù)量取決于集群中獨(dú)立 IP 的數(shù)量(注意不是 BE 數(shù)量)。Doris 中副本分布的原則是,不允許同一個(gè) Tablet 的副本分布在同一臺(tái)物理機(jī)上,而識(shí)別物理機(jī)即通過(guò) IP。所以,即使在同一臺(tái)物理機(jī)上部署了 3 個(gè)或更多 BE 實(shí)例,如果這些 BE 的 IP 相同,則依然只能設(shè)置副本數(shù)為 1。
- 對(duì)于一些小,并且更新不頻繁的維度表,可以考慮設(shè)置更多的副本數(shù)。這樣在 Join 查詢
- 時(shí),可以有更大的概率進(jìn)行本地?cái)?shù)據(jù) Join。
- storage_medium & storage_cooldown_time
- BE 的數(shù)據(jù)存儲(chǔ)目錄可以顯式的指定為 SSD 或者 HDD(通過(guò) .SSD 或者 .HDD 后綴區(qū)分)。建表時(shí),可以統(tǒng)一指定所有 Partition 初始存儲(chǔ)的介質(zhì)。注意,后綴作用是顯式指定磁盤(pán)介質(zhì),而不會(huì)檢查是否與實(shí)際介質(zhì)類(lèi)型相符。
(5)ENGINE
ENGINE 的類(lèi)型是 olap,即默認(rèn)的 ENGINE 類(lèi)型。在 Doris 中,只有這個(gè)ENGINE 類(lèi)型是由 Doris 負(fù)責(zé)數(shù)據(jù)管理和存儲(chǔ)的。其他 ENGINE 類(lèi)型,如 mysql、broker、es 等等,本質(zhì)上只是對(duì)外部其他數(shù)據(jù)庫(kù)或系統(tǒng)中的表的映射,以保證 Doris 可以讀取這些數(shù)據(jù)。而 Doris 本身并不創(chuàng)建、管理和存儲(chǔ)任何非 olap ENGINE 類(lèi)型的表和數(shù)據(jù)。
三、數(shù)據(jù)模型
Doris 的數(shù)據(jù)模型主要分為 3 類(lèi):Aggregate、Uniq、Duplicate
1. Aggregate 模型
表中的列按照是否設(shè)置了 AggregationType,分為 Key(維度列)和 Value(指標(biāo)列)。沒(méi)有設(shè)置 AggregationType 的稱為 Key,設(shè)置了 AggregationType 的稱為 Value。
當(dāng)導(dǎo)入數(shù)據(jù)時(shí),對(duì)于 Key 列相同的行會(huì)聚合成一行, Value 列會(huì)按照設(shè)置的AggregationType 進(jìn)行聚合。AggregationType 目前有以下四種聚合方式:
- SUM:求和,多行的 Value 進(jìn)行累加
- REPLACE:替代,下一批數(shù)據(jù)中的 Value 會(huì)替換之前導(dǎo)入過(guò)的行中的 Value。REPLACE_IF_NOT_NULL :當(dāng)遇到 null 值則不更新。
- MAX:保留最大值。
- MIN:保留最小值。
數(shù)據(jù)的聚合,在 Doris 中有如下三個(gè)階段發(fā)生:
(1)每一批次數(shù)據(jù)導(dǎo)入的 ETL 階段。該階段會(huì)在每一批次導(dǎo)入的數(shù)據(jù)內(nèi)部進(jìn)行聚合。
(2)底層 BE 進(jìn)行數(shù)據(jù) Compaction 的階段。該階段,BE 會(huì)對(duì)已導(dǎo)入的不同批次的數(shù)據(jù)進(jìn)行進(jìn)一步的聚合。
(3)數(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ā)生。
舉個(gè)栗子:導(dǎo)入數(shù)據(jù)聚合
CREATE TABLE IF NOT EXISTS test_db.example_site_visit ( `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 "用戶最后一次訪問(wèn)時(shí)間", `last_visit_date_not_null` DATETIME REPLACE_IF_NOT_NULL DEFAULT "1970-01-01 00:00:00" COMMENT "用戶最后一次訪問(wèn)時(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`, `city`, `age`, `sex`) DISTRIBUTED BY HASH(`user_id`) BUCKETS 10;
插入數(shù)據(jù):
執(zhí)行結(jié)果:
聚合發(fā)生在所有key列的值都一樣的情況下,類(lèi)似group by c1,c2,c3
Insert into 單條數(shù)據(jù)這種操作在 Doris 里不能在生產(chǎn)使用,會(huì)引發(fā)寫(xiě)阻塞,因?yàn)槊恳慌蔚臄?shù)據(jù)導(dǎo)入會(huì)引發(fā)數(shù)據(jù)的聚合邏輯操作。
再舉個(gè)栗子: 保留明細(xì)數(shù)據(jù)
CREATE TABLE IF NOT EXISTS test_db.example_site_visit2 ( `user_id` LARGEINT NOT NULL COMMENT "用戶 id", `date` DATE NOT NULL COMMENT "數(shù)據(jù)灌入日期時(shí)間", `timestamp` DATETIME 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 "用戶最后一次訪問(wèn)時(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`) DISTRIBUTED BY HASH(`user_id`) BUCKETS 10;
使用更細(xì)粒度的列從而讓Key列的值都不一樣,達(dá)到保存明細(xì)數(shù)據(jù)的效果,上述使用timestamp這種細(xì)粒度的列值,能夠記錄到每條數(shù)據(jù)。
2. Uniq 模型
在某些多維分析場(chǎng)景下,用戶更關(guān)注的是如何保證 Key 的唯一性,即如何獲得 Primary Key 唯一性約束。因此,我們引入了 Uniq 的數(shù)據(jù)模型。該模型本質(zhì)上是聚合模型的一個(gè)特例,也是一種簡(jiǎn)化的表結(jié)構(gòu)表示方式。
建表語(yǔ)句:
CREATE TABLE IF NOT EXISTS test_db.user ( `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`, `username`) DISTRIBUTED BY HASH(`user_id`) BUCKETS 10;
插入數(shù)據(jù):
insert into test_db.user values\ (10000,'wuyanzu',' 北 京 ',18,0,12345678910,' 北 京 朝 陽(yáng) 區(qū) ','2017-10-01 07:00:00'),\ (10000,'wuyanzu',' 北 京 ',19,0,12345678910,' 北 京 朝 陽(yáng) 區(qū) ','2017-10-01 07:00:00'),\ (10000,'zhangsan','北京',20,0,12345678910,'北京海淀區(qū)','2017-11-15 06:10:20');
執(zhí)行結(jié)果:
Unique模型確保了UNIQUE KEY(
user_id
,username
)的唯一行,而其他的列會(huì)選擇最新的列值。
3. Duplicate 模型
在某些多維分析場(chǎng)景下,數(shù)據(jù)既沒(méi)有主鍵,也沒(méi)有聚合需求。Duplicate 數(shù)據(jù)模型可以滿足這類(lèi)需求。數(shù)據(jù)完全按照導(dǎo)入文件中的數(shù)據(jù)進(jìn)行存儲(chǔ),不會(huì)有任何聚合。即使兩行數(shù)據(jù)完全相同,也都會(huì)保留。 而在建表語(yǔ)句中指定的 DUPLICATE KEY,只是用來(lái)指明底層數(shù)據(jù)按照那些列進(jìn)行排序。
4. 模型選建議
因?yàn)閿?shù)據(jù)模型在建表時(shí)就已經(jīng)確定,且無(wú)法修改。所以,選擇一個(gè)合適的數(shù)據(jù)模型非常重要。
(1)Aggregate 模型可以通過(guò)預(yù)聚合,極大地降低聚合查詢時(shí)所需掃描的數(shù)據(jù)量和查詢的計(jì)算量,非常適合有固定模式的報(bào)表類(lèi)查詢場(chǎng)景。但是該模型對(duì) count(*) 查詢很不友好。同時(shí)因?yàn)楣潭?Value 列上的聚合方式,在進(jìn)行其他類(lèi)型的聚合查詢時(shí),需要考慮語(yǔ)意正確性
(2)Uniq 模型針對(duì)需要唯一主鍵約束的場(chǎng)景,可以保證主鍵唯一性約束。但是無(wú)法利用 ROLLUP 等預(yù)聚合帶來(lái)的查詢優(yōu)勢(shì)(因?yàn)楸举|(zhì)是 REPLACE,沒(méi)有 SUM 這種聚合方式)。
(3)Duplicate 適合任意維度的 Ad-hoc 查詢。雖然同樣無(wú)法利用預(yù)聚合的特性,但是不 受聚合模型的約束,可以發(fā)揮列存模型的優(yōu)勢(shì)(只讀取相關(guān)列,而不需要讀取所有 Key 列)
如何應(yīng)對(duì)業(yè)務(wù)上的Count(*)場(chǎng)景問(wèn)題:
Doris提供的預(yù)聚合導(dǎo)致用戶在查詢時(shí),獲取的結(jié)果都是對(duì)key聚合后的結(jié)果,無(wú)法獲取到相應(yīng)key列的條數(shù)這樣的結(jié)果。
因此,當(dāng)業(yè)務(wù)上有頻繁的 count( * ) 查詢時(shí),增加一個(gè) count 列,并且導(dǎo)入數(shù)據(jù)中,該列值恒為 1。則 select count() from table; 的結(jié)果等價(jià)于 select sum(count) from table;。而后者的查詢效率將遠(yuǎn)高于前者。不過(guò)這種方式也有使用限制,就是用戶需要自行保證,不會(huì)重復(fù)導(dǎo)入AGGREGATE KEY 列都相同的行。否則,select sum(count) from table; 只能表述原始導(dǎo)入的行數(shù),而不是 select count() from table; 的語(yǔ)義。
另一種方式,就是 將如上的 count 列的聚合類(lèi)型改為 REPLACE,且依然值恒為 1。那
么 select sum(count) from table; 和 select count(*) from table; 的結(jié)果將是一致的。并且這種
方式,沒(méi)有導(dǎo)入重復(fù)行的限制。
四、ROLLUP
ROLLUP 在多維分析中是“上卷”的意思,即將數(shù)據(jù)按某種指定的粒度進(jìn)行進(jìn)一步聚合。
- 在 Doris 中,我們將用戶通過(guò)建表語(yǔ)句創(chuàng)建出來(lái)的表稱為 Base 表(Base Table)。Base 表中保存著按用戶建表語(yǔ)句指定的方式存儲(chǔ)的基礎(chǔ)數(shù)據(jù)。
- 在 Base 表之上,我們可以創(chuàng)建任意多個(gè) ROLLUP 表。這些 ROLLUP 的數(shù)據(jù)是基于 Base 表產(chǎn)生的,并且在物理上是獨(dú)立存儲(chǔ)的。
- ROLLUP 表的基本作用,在于在 Base 表的基礎(chǔ)上,獲得更粗粒度的聚合數(shù)據(jù)。
1. Aggregate模型的ROLLUP
因?yàn)?Uniq 只是 Aggregate 模型的一個(gè)特例,所以不加以區(qū)別。
查看表的結(jié)構(gòu)信息
例如想要查看每個(gè)用戶的總消費(fèi),建立一個(gè)只有 user_id 和 cost 的 rollup。alter table example_site_visit2 add rollup rollup_cost_userid(user_id,cost);
再次查看表結(jié)構(gòu)
- 創(chuàng)建rollup不會(huì)改變?cè)械谋斫Y(jié)構(gòu),并且對(duì)用戶而言是不可見(jiàn)的。
- 可以通過(guò) explain 查看執(zhí)行計(jì)劃,是否使用到了 rollup
- explain SELECT user_id, sum(cost) FROM example_site_visit2 GROUP BY user_id;
查看你ROLLUP表的完成狀態(tài)。
SHOW ALTER TABLE ROLLUP;
Aggregate模型的roolup表完成了對(duì)基表更粗粒度的聚合,具體的,當(dāng)數(shù)據(jù)插入到基表中時(shí),Doris會(huì)自動(dòng)將數(shù)據(jù)按照指定的聚合鍵進(jìn)行分組,并計(jì)算每個(gè)分組的聚合結(jié)果,roolup表也是如此完成數(shù)據(jù)的預(yù)聚合。
2. Duplicate 模型中的 ROLLUP
因?yàn)?Duplicate 模型沒(méi)有聚合的語(yǔ)意。所以該模型中的 ROLLUP,已經(jīng)失去了“上卷”這一層含義。而僅僅是作為調(diào)整列順序,以命中前綴索引的作用。
前綴索引
不同于傳統(tǒng)的數(shù)據(jù)庫(kù)設(shè)計(jì),Doris 不支持在任意列上創(chuàng)建索引。Doris 這類(lèi) MPP 架構(gòu)的 OLAP 數(shù)據(jù)庫(kù),通常都是通過(guò)提高并發(fā),來(lái)處理大量數(shù)據(jù)的。
本質(zhì)上,Doris 的數(shù)據(jù)存儲(chǔ)在類(lèi)似 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ǔ),是按照各自建表語(yǔ)句中,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 類(lèi)型時(shí),前綴索引會(huì)直接截?cái)唷?/strong>
以下表結(jié)構(gòu)的前綴索引為 user_id(8 Bytes) + age(4 Bytes) + message(prefix 20 Bytes)
[message列的一部分-取20個(gè)字節(jié)]。
ColumnName | ColumnType |
---|---|
user_id | BIGINT |
age | INT |
message | VARCHAR(100) |
max_dwell_time | DATETIME |
min_dwell_time | DATETIME |
以下表結(jié)構(gòu)的前綴索引為 user_name(20 Bytes)。即使沒(méi)有達(dá)到 36 個(gè)字節(jié),因?yàn)橛龅絍ARCHAR,所以直接截?cái)?,不再往后繼續(xù)。
ColumnName | ColumnType |
---|---|
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)行的查詢來(lái)說(shuō),效率上可能無(wú)法滿足需求。因此,我們可以通過(guò)創(chuàng)建 ROLLUP 來(lái)人為的調(diào)整列順序。
Base 表結(jié)構(gòu)如下:
ColumnName | ColumnType |
---|---|
user_id | BIGINT |
age | INT |
message | VARCHAR(100) |
max_dwell_time | DATETIME |
min_dwell_time | DATETIME |
我們可以在此基礎(chǔ)上創(chuàng)建一個(gè) ROLLUP 表:
ColumnName | ColumnType |
---|---|
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 message LIKE "%error%";
會(huì)優(yōu)先選擇 ROLLUP 表,因?yàn)?ROLLUP 的前綴索引匹配度更高。
3. DROLLUP 的使用說(shuō)明
- ROLLUP 最根本的作用是提高某些查詢的查詢效率(無(wú)論是通過(guò)聚合來(lái)減少數(shù)據(jù)量,還是修改列順序以匹配前綴索引)。因此 ROLLUP 的含義已經(jīng)超出了“上卷”的范圍。這也是為什么在源代碼中,將其命名為 Materialized 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 越多,占用的磁盤(pán)空間也就越大。同時(shí)對(duì)導(dǎo)入速度也會(huì)有影響(導(dǎo)入的 ETL 階段會(huì)自動(dòng)產(chǎn)生所有ROLLUP 的數(shù)據(jù)),但是不會(huì)降低查詢效率(只會(huì)更好)。
- ROLLUP 的數(shù)據(jù)更新與 Base 表是完全同步的。用戶無(wú)需關(guān)心這個(gè)問(wèn)題。
- ROLLUP 中列的聚合方式,與 Base 表完全相同。在創(chuàng)建 ROLLUP 無(wú)需指定,也不能修改。
- 查詢能否命中 ROLLUP 的一個(gè)必要條件(非充分條件)是,查詢所涉及的所有列(包括 select list 和 where 中的查詢條件列等)都存在于該 ROLLUP 的列中。否則,查詢只能命中 Base 表。
- 某些類(lèi)型的查詢(如 count(*))在任何條件下,都無(wú)法命中 ROLLUP。
- 可以通過(guò) EXPLAIN your_sql; 命令獲得查詢執(zhí)行計(jì)劃,在執(zhí)行計(jì)劃中,查看是否命中ROLLUP。
- 可以通過(guò) DESC tbl_name ALL; 語(yǔ)句顯示 Base 表和所有已創(chuàng)建完成的 ROLLUP。
五、物化視圖
物化視圖就是包含了查詢結(jié)果的數(shù)據(jù)庫(kù)對(duì)象,可能是對(duì)遠(yuǎn)程數(shù)據(jù)的本地 copy,也可能是一個(gè)表或多表 join 后結(jié)果的行或列的子集,也可能是聚合后的結(jié)果。說(shuō)白了,就是預(yù)先存儲(chǔ)查詢結(jié)果的一種數(shù)據(jù)庫(kù)對(duì)象。
在 Doris 中的物化視圖,就是查詢結(jié)果預(yù)先存儲(chǔ)起來(lái)的特殊的表。
物化視圖的出現(xiàn)主要是為了滿足用戶,既能對(duì)原始明細(xì)數(shù)據(jù)的任意維度分析,也能快速的對(duì)固定維度進(jìn)行分析查詢。
視圖是一種虛擬表,它是基于一個(gè)或多個(gè)數(shù)據(jù)庫(kù)表的查詢結(jié)果構(gòu)建而成的。視圖并不實(shí)際存儲(chǔ)數(shù)據(jù),而是根據(jù)定義的查詢邏輯,在查詢時(shí)動(dòng)態(tài)生成結(jié)果,可以理解為視圖是定義好的一組Sql語(yǔ)句,用戶查詢視圖時(shí),數(shù)據(jù)庫(kù)會(huì)根據(jù)視圖的定義執(zhí)行相應(yīng)的查詢操作,并返回結(jié)果。
物化視圖,是一種將查詢結(jié)果預(yù)先存儲(chǔ)起來(lái)的特殊的表。當(dāng)基表數(shù)據(jù)更新時(shí),物化視圖也會(huì)一同更新。
物化視圖創(chuàng)建刪除語(yǔ)法:
create materialized view 物化視圖名 as {sql語(yǔ)句} DROP MATERIALIZED VIEW 物化視圖名 on Base 表名;
適用場(chǎng)景:
- 分析需求覆蓋明細(xì)數(shù)據(jù)查詢以及固定維度查詢兩方面。
- 查詢僅涉及表中的很小一部分列或行。
- 查詢包含一些耗時(shí)處理操作,比如:時(shí)間很久的聚合操作等。
- 查詢需要匹配不同前綴索引。
優(yōu)勢(shì):
- 對(duì)于那些經(jīng)常重復(fù)的使用相同的子查詢結(jié)果的查詢性能大幅提升。
- Doris 自動(dòng)維護(hù)物化視圖的數(shù)據(jù),無(wú)論是新的導(dǎo)入,還是刪除操作都能保證 base 表和物化視圖表的數(shù)據(jù)一致性。無(wú)需任何額外的人工維護(hù)成本。
- 查詢時(shí),會(huì)自動(dòng)匹配到最優(yōu)物化視圖,并直接從物化視圖中讀取數(shù)據(jù)。自動(dòng)維護(hù)物化視圖的數(shù)據(jù)會(huì)造成一些維護(hù)開(kāi)銷(xiāo),會(huì)在后面的物化視圖的局限性中展開(kāi)說(shuō)明。
物化視圖 VS Rollup:
- 在沒(méi)有物化視圖功能之前,用戶一般都是使用 Rollup 功能通過(guò)預(yù)聚合方式提升查詢效率的。但是 Rollup 具有一定的局限性,他不能基于明細(xì)模型做預(yù)聚合。
- 物化視圖則在覆蓋了 Rollup 的功能的同時(shí),還能支持更豐富的聚合函數(shù)。所以物化視圖其實(shí)是 Rollup 的一個(gè)超集。
物化視圖不僅達(dá)到預(yù)聚合的目的,也能像rollup一樣修改字段順序,優(yōu)化前綴索引。
- ALTER TABLE ADD ROLLUP 語(yǔ)法支持的功能現(xiàn)在均可以通過(guò)CREATE MATERIALIZED VIEW 實(shí)現(xiàn)。
Doris 系統(tǒng)提供了一整套對(duì)物化視圖的 DDL 語(yǔ)法,包括創(chuàng)建,查看,刪除。DDL 的語(yǔ)法和PostgreSQL, Oracle 都是一致的。但是 Doris 目前創(chuàng)建物化視圖只能在單表操作,不支持 join。
1. 創(chuàng)建物化視圖
首先要根據(jù)查詢語(yǔ)句的特點(diǎn)來(lái)決定創(chuàng)建一個(gè)什么樣的物化視圖。并不是說(shuō)物化視圖定義和某個(gè)查詢語(yǔ)句一模一樣就最好。這里有兩個(gè)原則:
(1)從查詢語(yǔ)句中抽象出,多個(gè)查詢共有的分組和聚合方式作為物化視圖的定義。
(2)不需要給所有維度組合都創(chuàng)建物化視圖。
首先第一個(gè)點(diǎn),一個(gè)物化視圖如果抽象出來(lái),并且多個(gè)查詢都可以匹配到這張物化視圖。這種物化視圖效果最好。因?yàn)槲锘晥D的維護(hù)本身也需要消耗資源。如果物化視圖只和某個(gè)特殊的查詢很貼合,而其他查詢均用不到這個(gè)物化視圖。則會(huì)導(dǎo)致這張物化視圖的性價(jià)比不高,既占用了集群的存儲(chǔ)資源,還不能為更多的查詢服務(wù)。
第二點(diǎn)就是,在實(shí)際的分析查詢中,并不會(huì)覆蓋到所有的維度分析。所以給常用的維度組合創(chuàng)建物化視圖即可,從而到達(dá)一個(gè)空間和時(shí)間上的平衡。
創(chuàng)建物化視圖是一個(gè)異步的操作,也就是說(shuō)用戶成功提交創(chuàng)建任務(wù)后,Doris 會(huì)在后臺(tái)對(duì)存量的數(shù)據(jù)進(jìn)行計(jì)算,直到創(chuàng)建成功。在構(gòu)建期間,用戶依然可以正常的查詢和導(dǎo)入新的數(shù)據(jù)。創(chuàng)建任務(wù)會(huì)自動(dòng)處理當(dāng)前的存量數(shù)據(jù)和所有新到達(dá)的增量數(shù)據(jù),從而保持和 base 表的數(shù)據(jù)一致性。
2. 物化視圖的刪選匹配
銷(xiāo)售記錄表:Base表
Field | Type | Key | Extra |
---|---|---|---|
record_id | INT | TRUE | |
saller_id | INT | TRUE | |
store_id | INT | TRUE | |
sale_date | DATE | FALSE | |
sale_amt | BIGINT | FALSE |
mv_1: 存儲(chǔ)了不同時(shí)間不同銷(xiāo)售員的售賣(mài)量
Field | Type | Key | Extra |
---|---|---|---|
saller_id | INT | TRUE | |
sale_date | DATE | FALSE | |
sale_amt | BIGINT | FALSE | sum |
mv_2:存儲(chǔ)了不同時(shí)間不同門(mén)店的銷(xiāo)售量
Field | Type | Key | Extra |
---|---|---|---|
store_id | INT | TRUE | |
sale_date | DATE | FALSE | |
sale_amt | BIGINT | FALSE | sum |
mv_3:存儲(chǔ)了每個(gè)銷(xiāo)售員的總銷(xiāo)售量
Field | Type | Key | Extra |
---|---|---|---|
saller_id | INT | TRUE | |
sale_amt | BIGINT | FALSE | sum |
當(dāng)查詢 7 月 19 日,各個(gè)銷(xiāo)售員都買(mǎi)了多少錢(qián)的話。就可以匹配 mv_1 物化視圖。直接對(duì) mv_1 的數(shù)據(jù)進(jìn)行查詢。
物化視圖的自動(dòng)匹配分為下面兩個(gè)步驟:
(1)根據(jù)查詢條件刪選出一個(gè)最優(yōu)的物化視圖:這一步的輸入是所有候選物化視圖表的元數(shù)據(jù),根據(jù)查詢的條件從候選集中輸出最優(yōu)的一個(gè)物化視圖
- 第一層過(guò)濾先判斷查詢 where 中的謂詞涉及到的數(shù)據(jù)是否能從物化視圖中得到。以在這一層過(guò)濾中,mv_3 就被淘汰了。
- 第二層過(guò)濾查詢分組列是否為候選集的分組列的子集。在這一層過(guò)濾中,mv_2也被淘汰了。
- 第三層過(guò)濾是看查詢的聚合列是否為候選集中聚合列的子集。這里 base 表和物化視圖表均滿足標(biāo)準(zhǔn)。
- 最后一層是過(guò)濾看查詢需要的列是否存在于候選集合的列中。這里Base表和mv_1表均符合。
這里Base表和mv_1表均符合。
簡(jiǎn)而言之,物化視圖的篩選過(guò)程,就是按照sql關(guān)鍵字的執(zhí)行順序通過(guò)查詢字段和物化視圖的匹配程度逐一刪選出最符合的物化視圖。
候選集過(guò)濾完后輸出一個(gè)集合,這個(gè)集合中的所有表都能滿足查詢的需求。但每張表的查詢效率都不同。這時(shí)候就需要再這個(gè)集合根據(jù)前綴索引是否能匹配到,以及聚合程度的高低來(lái)選出一個(gè)最優(yōu)的物化視圖。
從表結(jié)構(gòu)中可以看出,base 表的銷(xiāo)售日期列是一個(gè)非排序列,而物化視圖表的日期是一個(gè)排序列,同時(shí)聚合程度上 mv_1 表明顯比 base 表高。所以最后選擇出 mv_1 作為該查詢的最優(yōu)匹配。
(2)根據(jù)選出的物化視圖對(duì)查詢進(jìn)行改寫(xiě):這一步是結(jié)合上一步選擇出的最優(yōu)物化視圖,進(jìn)行查詢的改寫(xiě),最終達(dá)到直接查詢物化視圖的目的。
查詢改寫(xiě)的原則:
其中 bitmap 和 hll 的聚合函數(shù)在查詢匹配到物化視圖后,查詢的聚合算子會(huì)根據(jù)物化視圖的表結(jié)構(gòu)進(jìn)行一個(gè)改寫(xiě)。
3. 物化視圖的限制
- 目前支持的聚合函數(shù)包括,常用的 sum,min,max count,以及計(jì)算 pv ,uv, 留存率,等常用的去重算法 hll_union,和用于精確去重計(jì)算 count(distinct)的算法bitmap_union。
- 物化視圖的聚合函數(shù)的參數(shù)不支持表達(dá)式僅支持單列,比如: sum(a+b)不支持。
- 使用物化視圖功能后,由于物化視圖實(shí)際上是損失了部分維度數(shù)據(jù)的。所以對(duì)表的 DML 類(lèi)型操作會(huì)有一些限制:
- 如果表的物化視圖 key 中不包含刪除語(yǔ)句中的條件列,則刪除語(yǔ)句不能執(zhí)行。
- 比如想要?jiǎng)h除渠道為 app 端的數(shù)據(jù),由于存在一個(gè)物化視圖并不包含渠道這個(gè)字段,則這個(gè)刪除不能執(zhí)行,因?yàn)閯h除在物化視圖中無(wú)法被執(zhí)行。這時(shí)候你只能把物化視圖先刪除,然后刪除完數(shù)據(jù)后,重新構(gòu)建一個(gè)新的物化視圖。
- 單表上過(guò)多的物化視圖會(huì)影響導(dǎo)入的效率:導(dǎo)入數(shù)據(jù)時(shí),物化視圖和 base 表數(shù)據(jù)是同步更新的,如果一張表的物化視圖表超過(guò) 10 張,則有可能導(dǎo)致導(dǎo)入速度很慢。這就像單次導(dǎo)入需要同時(shí)導(dǎo)入 10 張表數(shù)據(jù)是一樣的。
- 相同列,不同聚合函數(shù),不能同時(shí)出現(xiàn)在一張物化視圖中,比如:select sum(a), min(a) from table 不支持。
- 物化視圖針對(duì) Unique Key 數(shù)據(jù)模型,只能改變列順序,不能起到聚合的作用,所以在 Unique Key 模型上不能通過(guò)創(chuàng)建物化視圖的方式對(duì)數(shù)據(jù)進(jìn)行粗粒度聚合操作
六、修改和刪除語(yǔ)句
1. 修改表
使用 ALTER TABLE 命令可以對(duì)表進(jìn)行修改,包括 partition 、rollup、schema change、rename 和 index 五種。
rename
將名為 table1 的表修改為 table2
ALTER TABLE table1 RENAME table2;
將表 example_table 中名為 rollup1 的 rollup index 修改為 rollup2
ALTER TABLE example_table RENAME ROLLUP rollup1 rollup2;
將表 example_table 中名為 p1 的 partition 修改為 p2
ALTER TABLE example_table RENAME PARTITION p1 p2;
partition
增加分區(qū), 使用默認(rèn)分桶方式
現(xiàn)有分區(qū) [MIN, 2013-01-01),增加分區(qū) [2013-01-01, 2014-01-01),
ALTER TABLE example_db.my_table ADD PARTITION p1 VALUES LESS THAN ("2014-01-01");
增加分區(qū),使用新的分桶數(shù)
ALTER TABLE example_db.my_table ADD PARTITION p1 VALUES LESS THAN ("2015-01-01") DISTRIBUTED BY HASH(k1) BUCKETS 20;
增加分區(qū),使用新的副本數(shù)
ALTER TABLE example_db.my_table ADD PARTITION p1 VALUES LESS THAN ("2015-01-01") ("replication_num"="1");
修改分區(qū)副本數(shù)
ALTER TABLE example_db.my_table MODIFY PARTITION p1 SET("replication_num"="1");
批量修改指定分區(qū)
ALTER TABLE example_db.my_table MODIFY PARTITION (*) SET("storage_medium"="HDD");
批量修改所有分區(qū)
ALTER TABLE example_db.my_table MODIFY PARTITION (*) SET("storage_medium"="HDD");
刪除分區(qū)
ALTER TABLE example_db.my_table DROP PARTITION p1;
增加一個(gè)指定上下界的分區(qū)
ALTER TABLE example_db.my_table ADD PARTITION p1 VALUES [("2014-01-01"), ("2014-02-01"));
rollup
創(chuàng)建 index: example_rollup_index,基于 base index(k1,k2,k3,v1,v2)。列式存儲(chǔ)。
ALTER TABLE example_db.my_table ADD ROLLUP example_rollup_index(k1, k3, v1, v2);
創(chuàng)建 index: example_rollup_index2,基于 example_rollup_index(k1,k3,v1,v2)
ALTER TABLE example_db.my_table ADD ROLLUP example_rollup_index2 (k1, v1) FROM example_rollup_index;
創(chuàng)建 index: example_rollup_index3, 基于 base index (k1,k2,k3,v1), 自定義 rollup 超時(shí)時(shí)間一小時(shí)。
ALTER TABLE example_db.my_table ADD ROLLUP example_rollup_index(k1, k3, v1) PROPERTIES("timeout" = "3600");
刪除 index: example_rollup_index2
ALTER TABLE example_db.my_table DROP ROLLUP example_rollup_index2;
2. 刪除數(shù)據(jù)
Doris 目前可以通過(guò)兩種方式刪除數(shù)據(jù):DELETE FROM 語(yǔ)句和 ALTER TABLE DROP PARTITION 語(yǔ)句。
DELETE FROM Statement(條件刪除)
DELETE FROM table_name [PARTITION partition_name] WHERE column_name1 op { value | value_list } [ AND column_name2 op { value | value_list } ...];
delete from student_kafka where id=1;
- 該語(yǔ)句只能針對(duì) Partition 級(jí)別進(jìn)行刪除。如果一個(gè)表有多個(gè) partition 含有需要?jiǎng)h除的數(shù)據(jù),則需要執(zhí)行多次針對(duì)不同 Partition 的 delete 語(yǔ)句。而如果是沒(méi)有使用Partition 的表,partition 的名稱即表名。
- where 后面的條件謂詞只能針對(duì) Key 列,并且謂詞之間,只能通過(guò) AND 連接。如果想實(shí)現(xiàn) OR 的語(yǔ)義,需要執(zhí)行多條 delete。
- delete 是一個(gè)同步命令,命令返回即表示執(zhí)行成功。
- 從代碼實(shí)現(xiàn)角度,delete 是一種特殊的導(dǎo)入操作。該命令所導(dǎo)入的內(nèi)容,也是一個(gè)新的數(shù)據(jù)版本,只是該版本中只包含命令中指定的刪除條件。在實(shí)際執(zhí)行查詢時(shí),會(huì)根據(jù)這些條件進(jìn)行查詢時(shí)過(guò)濾。所以,不建議大量頻繁使用 delete 命令,因?yàn)檫@可能導(dǎo)致查詢效率降低。
- 數(shù)據(jù)的真正刪除是在 BE 進(jìn)行數(shù)據(jù) Compaction 時(shí)進(jìn)行的。所以執(zhí)行完 delete 命令后,并不會(huì)立即釋放磁盤(pán)空間。
- delete 命令一個(gè)較強(qiáng)的限制條件是,在執(zhí)行該命令時(shí),對(duì)應(yīng)的表,不能有正在進(jìn)行的導(dǎo)入任務(wù)(包括 PENDING、ETL、LOADING)。而如果有 QUORUM_FINISHED 狀態(tài)的導(dǎo)入任務(wù),則可能可以執(zhí)行。
- delete 也有一個(gè)隱含的類(lèi)似 QUORUM_FINISHED 的狀態(tài)。即如果 delete 只在多數(shù)副本上完成了,也會(huì)返回用戶成功。但是會(huì)在后臺(tái)生成一個(gè)異步的 delete job(Async Delete Job),來(lái)繼續(xù)完成對(duì)剩余副本的刪除操作。如果此時(shí)通過(guò) show delete 命令,可以看到這種任務(wù)在 state 一欄會(huì)顯示 QUORUM_FINISHED。
DROP PARTITION Statement(刪除分區(qū))
該命令可以直接刪除指定的分區(qū)。因?yàn)?Partition 是邏輯上最小的數(shù)據(jù)管理單元,所以使用 DROP PARTITION 命令可以很輕量的完成數(shù)據(jù)刪除工作。并且該命令不受 load 以及任何其他操作的限制,同時(shí)不會(huì)影響查詢效率。是比較推薦的一種數(shù)據(jù)刪除方式。
該命令是同步命令,執(zhí)行成功即生效。而后臺(tái)數(shù)據(jù)真正刪除的時(shí)間可能會(huì)延遲 10 分鐘左右。
七、數(shù)據(jù)的導(dǎo)入導(dǎo)出
導(dǎo)入(Load)功能就是將用戶的原始數(shù)據(jù)導(dǎo)入到 Doris 中。導(dǎo)入成功后,用戶即可通過(guò)Mysql 客戶端查詢數(shù)據(jù)。
為適配不同的數(shù)據(jù)導(dǎo)入需求,Doris 系統(tǒng)提供了 6 種不同的導(dǎo)入方式。每種導(dǎo)入方式支持不同的數(shù)據(jù)源,存在不同的使用方式(異步,同步)。
所有導(dǎo)入方式都支持 csv 數(shù)據(jù)格式。其中 Broker load 還支持 parquet 和 orc 數(shù)據(jù)格式。
- Broker load
通過(guò) Broker 進(jìn)程訪問(wèn)并讀取外部數(shù)據(jù)源(如 HDFS)導(dǎo)入到 Doris。用戶通過(guò) Mysql協(xié)議提交導(dǎo)入作業(yè)后,異步執(zhí)行。通過(guò) SHOW LOAD 命令查看導(dǎo)入結(jié)果。
- Stream load
用戶通過(guò) HTTP 協(xié)議提交請(qǐng)求并攜帶原始數(shù)據(jù)創(chuàng)建導(dǎo)入。主要用于快速將本地文件或數(shù)據(jù)流中的數(shù)據(jù)導(dǎo)入到 Doris。導(dǎo)入命令同步返回導(dǎo)入結(jié)果。
- Insert
類(lèi)似 MySQL 中的 Insert 語(yǔ)句,Doris 提供 INSERT INTO tbl SELECT …; 的方式從Doris 的表中讀取數(shù)據(jù)并導(dǎo)入到另一張表?;蛘咄ㄟ^(guò) INSERT INTO tbl VALUES(…); 插入單條數(shù)據(jù)。
- Multi load
用戶通過(guò) HTTP 協(xié)議提交多個(gè)導(dǎo)入作業(yè)。Multi Load 可以保證多個(gè)導(dǎo)入作業(yè)的原子生效。
Routine load
- 用戶通過(guò) MySQL 協(xié)議提交例行導(dǎo)入作業(yè),生成一個(gè)常駐線程,不間斷的從數(shù)據(jù)源(如Kafka)中讀取數(shù)據(jù)并導(dǎo)入到 Doris 中。
通過(guò) S3 協(xié)議直接導(dǎo)入
- 用戶通過(guò) S3 協(xié)議直接導(dǎo)入數(shù)據(jù),用法和 Broker Load 類(lèi)似。Broker load 是一個(gè)異步的導(dǎo)入方式,支持的數(shù)據(jù)源取決于 Broker 進(jìn)程支持的數(shù)據(jù)源。用戶需要通過(guò) MySQL 協(xié)議創(chuàng)建 Broker load 導(dǎo)入,并通過(guò)查看導(dǎo)入命令檢查導(dǎo)入結(jié)果。
本文不再贅述數(shù)據(jù)導(dǎo)入導(dǎo)出,具體的可前往官網(wǎng)查看。
到此這篇關(guān)于Apache-Doris基礎(chǔ)概念的文章就介紹到這了,更多相關(guān)Apache Doris內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
淺談Linux內(nèi)核創(chuàng)建新進(jìn)程的全過(guò)程
這篇文章主要為大家深入淺出的介紹了Linux內(nèi)核創(chuàng)建新進(jìn)程的全過(guò)程,感興趣的小伙伴們可以參考一下2016-01-01Centos7的apache網(wǎng)站環(huán)境搭建wordpress
本篇文章給大家詳細(xì)分析了在Centos7的apache網(wǎng)站環(huán)境搭建wordpress的詳細(xì)操作方法,有興趣的朋友參考下。2018-02-02用DNSPod和Squid打造自己的CDN (三) 安裝CentOS Linux
這篇文章主要介紹安裝CentOS Linux的一些步驟,大家可以繼續(xù)查看下一章2013-04-04Windows10使用Linux子系統(tǒng)實(shí)現(xiàn)輕松安裝多個(gè)linux
這篇文章主要為大家學(xué)習(xí)介紹了Windows10如何使用Linux子系統(tǒng)實(shí)現(xiàn)輕輕松松安裝多個(gè)linux,本文通過(guò)圖文為大家進(jìn)行了詳細(xì)介紹,需要的可以收藏一下2023-08-08Linux?AMH?服務(wù)器管理面板遠(yuǎn)程訪問(wèn)的操作方法
AMH?是一款基于?Linux?系統(tǒng)的服務(wù)器管理面板,它提供了一系列的功能,包括網(wǎng)站管理、FTP?管理、數(shù)據(jù)庫(kù)管理、DNS?管理、SSL?證書(shū)管理等,本文介紹在Linux?中安裝AMH面板并結(jié)合Cpolar?內(nèi)網(wǎng)穿透工具實(shí)現(xiàn)遠(yuǎn)程訪問(wèn),感興趣的朋友一起看看吧2023-11-11關(guān)于Windows 不能在 本地計(jì)算器 啟動(dòng) Apache2(phpstudy)
今天在自己的本子上準(zhǔn)備放多個(gè)虛擬站點(diǎn)。用的是#phpstudy#。在軟件自身的站點(diǎn)設(shè)置中,根據(jù)提示添加的多站點(diǎn)無(wú)效不知道是否和我的系統(tǒng)是Win7有關(guān)2012-09-09Tomcat無(wú)法加載css和js等靜態(tài)資源文件的解決思路
Tomcat無(wú)法加載css和js等靜態(tài)資源文件的情況想必從事相關(guān)行業(yè)的工作人員都有遇到過(guò)吧,接下來(lái)為大家介紹下詳細(xì)的解決方法,感興趣的朋友可以參考下2013-10-10Linux parted磁盤(pán)分區(qū)實(shí)現(xiàn)步驟解析
這篇文章主要介紹了Linux parted磁盤(pán)分區(qū)實(shí)現(xiàn)步驟解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08