摘要:表結構設計是數據庫建模的一個關鍵環節,表定義好壞直接決定了集羣的有效容量以及業務查詢性能,本文從產品架構、功能實現以及業務特徵的角度闡述在GaussDB(DWS)的中表定義時須要關注的一些關鍵因素。
GaussDB(DWS)是企業級的大規模並行處理關係型數據庫,採用Shared-nothing架構的MPP(Massive Parallel Processing)系統,支持PB級別數據量的處理,適用於詳單查詢、數據倉庫、混合負載和大數據分析等場景。Shared-nothing架構自然支持數據打散分佈到各個數據節點(DataNode)以及多節點協同計算機制,同時這種機制對錶定義涉及提出了更高的訴求,表定義會直接影響集羣的有效容量以及業務查詢性能。本文從產品架構、功能實現以及業務特徵的角度闡述GaussDB(DWS)的中表定義須要關注的一些關鍵因素。html
GaussDB(DWS)支持行存儲(row-based storage)和列存儲(column-based storage)兩種存儲方式,這兩種存儲格式分別適用不一樣的業務場景。一般來說典型的點查詢爲主的場景推薦使用行存儲,典型的統計分析型業務推薦使用列存儲。web
1.1 行存儲數據庫
行存儲模式下,一條數據的全部列組合在一塊兒稱之爲一個tuple多個tuple組成一個page,全部的page構成表的數據文件。pages是行存數據存取的最小單元,一個page默認8KB。page的基本結構以下segmentfault
行存儲模式下,全部數據列集中存儲在一個tuple中,因此行存儲的更新(UPDATE)、刪除(DELETE)、索引點查性能較好,可是當查詢列只涉及全部列的不多一部分的時候,全部列的數據也都會被讀取,致使大量的無效IO,所以推薦比較簡單點查場景或者存在頻繁的數據更新的業務採用行存儲。性能優化
1.2 列存儲網絡
列存儲下把數據表中的每一列單獨存儲,每一個列的 6w條數據組成一個CU,每一個列的全部的CU構成一個列的數據文件,每一個列都會有單獨的數據文件。CU的基本結構以下架構
列數據之間具備更高的類似度,因此列存儲的壓縮性能更好。當只查詢少許列且查詢數據量較大時,列存儲的IO性能收益很明顯。由於數據分列存儲,致使更新(UPDATE)、刪除(DELETE)、索引點查性的時候須要訪問或者刷新更多的文件,致使大量的隨機IO;所以相比行存儲,列存儲的更新、刪除、索引點查詢的性能較差。同時列存儲自然的能夠跟向量化執行引擎對接,在表關聯、匯聚等重計算場景下可使用向量化執行引擎提高計算性能,所以統計分析等重IO和重計算型業務推薦使用列存儲。併發
1.3 表存儲方式選擇框架
表的存儲類型是表定義設計的第一步,客戶業務屬性是表的存儲類型的決定性因素。根據以上咱們對行存儲和列存儲原理的介紹,重查詢分析(大量的多表關聯、group by操做)場景推薦使用使用列存表,典型的有數倉場景;以點查詢爲主的場景推薦使用行存表,典型的有詳單查詢場景。分佈式
GaussDB(DWS)支持單個database中同時存儲行存儲和列存儲類型的表,以更好的支持混合負載場景
1.4 表存儲方式定義
表的行/列存儲經過表定義的orientation屬性定義。當指定orientation屬性爲row時,表爲行存儲;當指定orientation屬性爲column時,表爲列存儲;若是不指定,默認爲行存儲。
行存儲表定義方式以下:
CREATE TABLE storage ( c_id int, c_d_id int NOT NULL, c_w_id int NOT NULL, c_first varchar(16) NOT NULL )WITH(orientation=row) DISTRIBUTE BY HASH(c_d_id);
列存儲表定義方式以下:
CREATE TABLE storage ( c_id int, c_d_id int NOT NULL, c_w_id int NOT NULL, c_first varchar(16) NOT NULL )WITH(orientation=column) DISTRIBUTE BY HASH(c_d_id);
GaussDB(DWS)的MPP架構,自然支持經過散列的方式進行水平分表,將業務數據表的元組打散存儲到各個數據節點(DataNode)上,經過並行利用各個數據節點的IO能力提高數據掃描的效率。爲了優化高頻關聯小表的查詢性能,GuassDB(DWS)支持複製的數據分佈方式。表的分佈方式取決於表的業務屬性,事實表通常數據量較大,且數據增長或者變化很頻繁,建議使用散列分佈;維度表數據量較小,且數據通常不會變化,只有按期更新操做,建議使用複製分佈。
2.1 散列分佈
散列分佈是按照某種散列規則,把表數據map到指定的數據節點(DataNode)上進行存儲的方式。散列分佈能夠利用各個節點的IO資源,提高各個數據節點的IO能力。GaussDB(DWS)中採用hash的散列策略,按照表定義時指定的分佈列組合,對一條記錄的某一個或幾個字段進行hash運算後,生成對應的hash值,而後根據DN實例與哈希值的映射關係得到該元組的目標存儲位置。
對於散列分佈的表,分佈列的選擇很是重要。當分佈列選擇合理時,Hash散列策略能夠大大減少計算節點之間的數據交互,大幅提高查詢性能;可是當hash分佈列選擇不合理時,會致使數據傾斜(某個或者某些DataNode的數據量嚴重超過其它DataNode的數據量),由於短板效應致使集羣的有效容量降低。
散列主要使用於客戶業務表,這些表有數據量大、數據量逐漸增長的特徵,適用散列分佈能夠有效的提高表查詢性能。
2.2 複製分佈
複製分佈(replication)策略將表中的全量數據在集羣的每個DN實例上保留一份。在關聯操做中複製表能夠避免數據重分佈操做,減少網絡開銷,同時減小了plan segment(每一個plan segment都會起對應的線程)的個數;可是複製分佈策略會致使比較嚴重的數據冗餘,所以只有小表才適合複製分佈策略。
實際生產上只有小數據量、查詢頻繁、更新(DELETE/INSERT/UFPATE)不多的表(基本都是維度表)纔會定義replication分佈策略
2.3 分佈方式選擇
表數據分佈方式主要依據表的業務屬性和數據屬性決定,簡單總結以下
2.4 分佈列定義
表的複製分佈屬性能夠經過表定義指定:
CREATE TABLE storage ( c_id int, c_d_id int NOT NULL, c_w_id int NOT NULL, c_first varchar(16) NOT NULL )WITH(orientation=row) DISTRIBUTE BY REPLICATRRION;
表的散列分分佈屬性能夠經過表定義:
CREATE TABLE storage ( c_id int, c_d_id int NOT NULL, c_w_id int NOT NULL, c_first varchar(16) NOT NULL )WITH(orientation=row) DISTRIBUTE BY HASH(c_d_id);
對於採起散列分佈策略的表,分佈列的選擇取決於表數據的特徵以及表相關的業務查詢特徵,推薦使用常常作關聯查詢的列、且數據分佈均勻的列做爲分佈列。好的分佈列能夠經過減小跨節點的數據計劃節省網絡資源開銷,優化查詢性能。
3.1 分佈列選擇策略
Hash分佈表的分佈列選取相當重要,須要知足如下原則:
a) 列值應比較離散,以便數據可以均勻分佈到各個DN
分佈列值分佈不均勻會致使數據在數據節點分佈不均勻(某些DataNode上數據量大,某些DataNode上數據量小),這會致使不一樣DataNode上數據掃面的計算量不均衡,從而拖慢整個表掃描的性能;同時會由於部分DataNode的磁盤容量提早爆滿,集羣只讀,致使集羣有效容量降低。一般狀況下使用表的主鍵列或者惟一索引列做爲表的分佈列是一個不錯的選擇
b) 考慮選擇查詢中的鏈接條件爲分佈列
GaussDB(DWS)的散列策略是hash,根據GaussDB(DWS)的分佈式查詢框架,當兩表等值關聯(join)列恰好是表的分佈列時(若是分佈列是多列,那麼要求全部列都存在等值關聯條件),join任務能夠再也不數據重分佈的狀況下直接Join,這樣能夠省去數據重分佈的時間開銷和網絡資源開銷,從而提高查詢計算性能。
c) 在知足前面兩條原則的狀況下儘可能不要選取存在常量等值filter的列
GaussDB(DWS)會協調節點(Coordinator)上進行任務規劃,此時會根據表的過濾條件(Filter)進行掃面操做剪枝優化,以較小IO資源開銷。若是表dwcjk的分佈列是zqdh,且表dwcjk掃描時存在Filter條件zqdh=’000001’,而根據散列策略zqdh=’000001’的值都分佈在數據節點DN1上,那麼協調節點(Coordinator)上進行任務規劃時會對dwcjk表的掃描操做進行剪枝(指定只有在數據節點DN1對錶dwcjk進行數據掃描操做)。這樣對於表掃描的實際壓力會值落在節點DN1,致使不一樣數據節點的IO壓力不均衡。
注意此策略主要適用於統計分析類的重查詢場景,對於詳單查詢等以點查爲主要場景的查詢類業務,在知足前兩個約束的前提下,能夠優選存在常量等值Filter約束列做爲分佈列。由於這種場景在數據節點上使用索引加速查詢,查詢耗時每每以ms或者幾十ms計,經過剪枝把查詢任務map到具體的某個數據節點上執行,節省無效操做(不用鏈接到全部的數據節點上操做),同時也會大大的提升併發能力
3.2 分佈列選擇的限制
GaussDB(DWS)的列存儲格式的表不支持主鍵和惟一約束,行存儲格式表支持主鍵和惟一約束。可是存儲格式表的主鍵和惟一約束的建立存在嚴格約束:分佈列的集合是主鍵列或者索引列的子集。
多個列做爲分佈列時,分佈列的順序會影響數據分佈,即同一條記錄在distribute by hash(col1, col2)方式下,跟在distribute by hash(col2, col1)分佈方式下可能會map到不一樣的DataNode上進行存儲。
GaussDB(DWS)對分佈列的個數沒有限制,可是建議分佈列的個數儘可能少,一方面能夠減少數據map到不一樣DN的計算開銷,同時也能夠更好的全匹配join條件,提高查詢性能。
3.3 分佈列離散性校驗
對於當前已建立而且導入數據的表,可使用以下SQL檢驗表數據分佈的離散型
-- 'public'是表的schema名稱,'storage'是表名 SELECT * FROM table_distribution('public.storage') ORDER BY dnsize;
對於已經建立而且導入數據的表,若是咱們認爲當前的分佈列不夠離散,在修改成其它列以前,可嘗試使用以下SQL判斷目標分佈列的離散性
-- 'public'是表的schema名稱,'storage'是表名,c_id是要檢測的列名 SELECT * FROM table_skewness('public.storage', 'c_id') ORDER BY seqnum;
當肯定目標分佈列以後,可使用以下SQL實現分佈列的修改
-- 'public'是表的schema名稱,'storage'是表名,c_id是修改後的目標分佈列 ALTER TABLE public.storage DISTRIBUTE BY HASH(c_id);
通俗的講表,分區就是把一個大表按照條件分割爲若干個小表,這種分割體如今數據庫內部的數據管理上,對錶數據的常規操做(UPDATE/DELETE/INSERT/SELECT)是透明的。通常對數據和查詢都有明顯區間段特徵的表使用分區策略,可經過減小沒必要要的數據掃描提高查詢性能。以下case中,使用分區表能夠減小60%的數據掃描量,SQL查詢總體性能提高46左右。
4.1 分區表的優點
分區表把邏輯上的一張表根據範圍分區策略分紅幾張物理塊庫進行存儲,這張邏輯上的表稱之爲分區表,物理塊稱之爲分區。分區表是一張邏輯表,不存儲數據,數據實際是存儲在分區上的。分區表和普通表相比具備如下優勢:
a) 改善查詢性能
對分區對象的查詢能夠僅搜索本身關心的分區,提升檢索效率
b) 加強可用性
若是分區表的某個分區出現故障,表在其餘分區的數據仍然可用
c) 提高可維護性
對於須要週期性刪除的過時歷史數據,能夠經過drop/truncate分區的方式快速高效處理
4.2 分區策略選擇
當表有如下特徵時,能夠考慮使用表分區策略
a) 數據具備明顯區間性的字段
分區表須要根據有明顯區間性字段進行表分區。一般咱們好比日期、區域、數值等字段進行分區,時間字段是最多見的分區字段。
b) 業務查詢有明顯的區間範圍特徵
查詢數據可落到區間範圍指定的分區內,這樣才能經過分區剪枝,只掃描查詢須要的分區,從而提高數據掃描效率,下降數據掃描的IO開銷。
c) 表數據量比較大
小表掃描自己耗時不大,分區表的性能收益不明顯,所以只建議對大表採起分區策略。列存儲模式下由於每一個列是單獨的文件出處,且最小的存儲單元CU可存儲6w行數據,所以對於列存分區表,建議每一個分區的數據不小於DN個數*6w
4.3 分區表定義
分區表策略定義分爲兩種方式
a) 簡單區間切割
這種是最多見的通用的分區定義策略,適合全部的分區定義場景。
CREATE TABLE web_returns_p1 ( wr_returned_date_sk integer, wr_returned_time_sk integer, wr_item_sk integer NOT NULL, wr_refunded_customer_sk integer ) WITH (orientation = column) DISTRIBUTE BY HASH (wr_item_sk) PARTITION BY RANGE(wr_returned_date_sk) ( PARTITION p2016 VALUES LESS THAN(20161231), PARTITION p2017 VALUES LESS THAN(20171231), PARTITION p2018 VALUES LESS THAN(20181231), PARTITION p2019 VALUES LESS THAN(20191231), PARTITION pxxxx VALUES LESS THAN(maxvalue) );
b) 指定策略切割
此方式適用於分區間隔固定、批量建立分區的場景。當分區個數不少時,此方式可大大節省建立分區的工做量
CREATE TABLE web_returns_p1 ( wr_returned_date_sk integer, wr_returned_time_sk integer, wr_item_sk integer NOT NULL, wr_refunded_customer_sk integer ) WITH (orientation = column) DISTRIBUTE BY HASH (wr_item_sk) PARTITION BY RANGE(wr_returned_date_sk) ( PARTITION p2016 START(20161231) END(20191231) EVERY(10000), PARTITION p0 END(maxvalue) );
4.4 分區鍵選擇限制
相似表分佈列的選擇,對於行存儲格式的表,若是表存在主鍵或者惟一約束,分區鍵應當是是主鍵列或者惟一約束的索引列的子集。
4.5 分區表查詢
只有查詢語句能夠進行分區剪枝的時候,分區表查詢纔會產生數據掃描上的性能收益。通常只有當分區鍵跟常量值存在直接的比較(>、<、=、<=、>=)操做時,分區表才能夠正常剪枝。咱們能夠經過對查詢語句執行explain命令查看分區剪枝的效果
有時咱們指望編寫的SQL語句能夠進行分區剪枝,可是實際上並無走到分區剪枝,這種預期外的行爲每每是由於如下因素致使
a) 分區鍵上有函數
當分區鍵上存在函數調用時,分區表沒法剪枝
b) 分區鍵字段的存在隱式類型轉換
這種場景每每是由於分區鍵跟常量值的數據類型不一致,致使計劃規劃時分區鍵的數據類型發生隱式類型轉換,致使分區沒法剪枝
表字段設計時須要注意如下內容
a) 使用執行效率比較高的數據類型
通常來講整型數據的運算(包括=、>、<、≧、≦、≠等常規的比較運算,以及group by等運算)效率比字符串、浮點數要高。能使用整型的場景儘可能使用整型。
b) 使用短字段的數據類型
長度較短的數據類型不只能夠減少數據文件的大小,提高IO性能;同時也能夠減少計算相關計算時的內存消耗,提高計算性能。好比咱們須要一個整型數據,若是能夠用smallint就儘可能不用int,若是能夠用int就儘可能不用bigint。
c) 關聯列使用一致的數據類型
表關聯列儘可能使用相同的數據類型。若是表關聯列數據類型不一樣,在執行時數據庫會動態地轉化爲相同的數據類型進行比較,這種轉換會帶來必定的性能開銷,同時可能會由於類型轉換致使表關聯操做時發生數據重分佈,致使額外的性能和資源開銷。
1) 非空(not null)約束
明確不存在null值的字段加上not null約束。在特定場景下,優化器會對包含not null的查詢語句進行自動優化,提高查詢效率。
2) 主鍵/惟一約束
行存儲表支持惟一/主鍵約束,若是表是散列分佈,那麼約束列必須包含全部的分佈列;若是表作了分區,那麼約束列也必須包含全部的分區列。
3) 局部聚簇約束
局部聚簇(partial cluster key,簡稱PCK)是列存儲表一種局部聚簇技術,這種技術可讓表數據在批量入庫的時,先對錶進行局部排序,而後再寫盤。這樣可讓相同/類似的數據連續存儲,提升數據的壓縮比;同時在查詢時也能夠依賴列存儲表的min/max稀疏索引實現表的CU過濾,從實現高效的數據過濾效果(參見《GaussDB(DWS)性能調優:列存表scan性能優化》)。一張表上只能創建一個PCK,一個PCK能夠包含多列,可是通常不建議超過2列。一般咱們使用常常出現的、過濾效果比較好的簡單表達式中的列做爲PCK列,局部聚簇約束的定義方式跟主鍵約束的定義方式相似
CREATE TABLE web_returns_p1 ( wr_returned_date_sk integer, wr_returned_time_sk integer, wr_item_sk integer NOT NULL, wr_refunded_customer_sk integer, PARTIAL CLUSTER KEY(wr_returned_date_sk) ) WITH (orientation = column) DISTRIBUTE BY HASH (wr_item_sk);
最後簡單總結下表定義流程