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