Greenplum 性能優化之路 --(三)ANALYZE


一、為什么需要 ANALYZE

 

首先介紹下 RBO 和 CBO,這是數據庫引擎在執行 SQL 語句時的2種不同的優化策略。

 

RBO(Rule-Based Optimizer)

基於規則的優化器,就是優化器在優化查詢計划的時候,是根據預先設置好的規則進行的,這些規則無法靈活改變。舉個例子,索引優先於掃描,這是一個規則,優化器在遇到所有可以利用索引的地方,都不會選擇掃描。這在多數情況下是正確的,但也不完全如此:

 

比如 一張個人信息表中性別欄目加上索引,由於性別是只有2個值的枚舉類,也就是常說的基數非常低的列,在這種列上使用索引往往效果還不如掃描

 
SELET count(*) FROM person WHERE gender = 'M';

  

因此 RBO 的優化方式是死板的,粗放的,目前已逐漸被 CBO 方式取代。

CBO(Cost Based Optimizer)

 

基於代價的優化器,就是優化器在優化查詢計划的時候,是根據動態計算出來的 Cost(代價)來判斷如何進行選擇。那如何計算代價呢?這里一般是基於代價模型和統計信息,代價模型是否合理,統計信息是否准確都會影響優化的效果。

 

還是拿上面員工性別統計為例,在 CBO 的優化方式下,物理計划就不會選擇走索引。當然上面的例子比較簡單,在 Greenplum 運行的復雜 SQL 中,優化器最核心的還是在 scan 和 join 的各種實現方式中做出選擇,這才是能大幅提升性能的關鍵點。

 

前面提到 CBO 需要一個代價模型和統計信息,代價模型和規則一樣,需要預先設置好,那統計信息是如何收集的?多數基於 CBO 優化的計算引擎,包括 Greenplum,Oracle,Hive,Spark 等都類似,除了可以按一定規則自動收集統計信息外,還都支持手動輸入命令進行收集,通常這個命令都叫 ANALYZE。

 

結論:由於 CBO 優化的需求,因此我們需要使用 ANALYZE 命令去收集統計信息。

 

二、ANALYZE 怎么使用

 

說明

 

ANALYZE 是 Greenplum 提供的收集統計信息的命令。

ANALYZE 支持三種粒度,列,表,庫,如下:

CREATE TABLE foo (id int NOT NULL, bar text NOT NULL) DISTRIBUTED BY (id); // 創建測試表fooANALYZE foo(bar); // 只搜集bar列的統計信息ANALYZE foo; // 搜集foo表的統計信息ANALYZE; // 搜集當前庫所有表的統計信息,需要有權限才行

 

限制

ANALYZE 會給目標表加 SHARE UPDATE EXCLUSIVE 鎖,也就是與 UPDATE,DELETE,還有 DDL 語句沖突。

 

速度

ANALYZE 是一種采樣統計算法,通常不會掃描表中所有的數據,但是對於大表,也仍會消耗一定的時間和計算資源。

 

采樣統計會有精度的問題,因此 Greenplum 也提供了一個參數 default_statistics_target,調整采樣的比例。簡單說來,這個值設置得越大,采樣的數量就越多,准確性就越高,但是消耗的時間和資源也越多。

 

default_statistics_target.png

 

直接修改服務器的參數會影響整個集群,通常不建議這樣操作。如果確實有需要,可以嘗試只修改某列的對應參數,如下:

 
ALTER TABLE {table_name} ALTER COLUMN {col_name} SET STATISTICS {-1|0-1000};

  

時機

根據上文所述,ANALYZE 會加鎖並且也會消耗系統資源,因此運行命令需要選擇合適的時機盡可能少的運行。根據 Greenplum 官網建議,以下3種情況發生后建議運行 ANALYZE

  • 批量加載數據后,比如 COPY

  • 創建索引之后

  • INSERT, UPDATE, and DELETE 大量數據之后

自動化

除了手動運行,ANALYZE 也可以自動化。實際上默認情況下,我們對空表寫入數據后, Greenplum 也會自動幫我們收集統計信息,不過之后在寫入數據,就需要手動操作了。

有2個參數可以用來調整自動化收集的時機,gp_autostats_mode 和 gp_autostats_on_change_threshold。gp_autostats_mode 默認是 on_no_stats,也就是如果表還沒有統計信息,這時候寫入數據會導致自動收集,這之后,無論表數據變化多大,都只能手動收集了。如果將 gp_autostats_mode 修改為 on_change ,就是在數據變化量達到 gp_autostats_on_change_threshold 參數配置的量之后,系統就會自動收集統計信息。

 

分區表

 

Greenplum 官網對於分區表的 ANALYZE 專門進行了講解,其實只要保持默認值,不去修改系統參數 optimizer_analyze_root_partition,那么對於分區表的操作並沒有什么不同,直接在 root 表上進行 ANALYZE 即可,系統會自動把所有葉子節點的分區表的統計信息都收集起來。

 

如果分區表的數目很多,那在 root 表上進行 ANALYZE 可能會非常耗時,通常的分區表都是帶有時間維度的,歷史的分區表並不會修改,因此單獨 ANALYZE 數據發生變化的分區,是更好的實踐。

 

三、統計信息去了哪里

 

pg_class

 

表的大小是統計信息里面最直觀,也幾乎是最重要的,這個信息是放在 pg_catalog.pg_class 系統表中,reltuples 代表元組數(行數),relpages 代表實際占用的 page 數目(Greenplum中一個 page 為32KB)。

 

需要注意以下3點

 

1. reltuples 不是准確值,獲取表的准確行數還是需要 count。

 

2. reltuples 和 relpages 需要通過 ANALYZE 進行收集,對於已有數據的表,系統不會自動更新。

 

3. reltuples 和 relpages 不一定能對齊,比如條數看起來不多的表,實際占用的 page 數目很大,這種一般是由於數據膨脹(bloat)造成,這時候需要 vacuum 等操作。

 

pg_statistic

 

關於列的統計信息都是存放在 pg_catalog.pg_statistic 系統表中。其中表的每一列(如果有統計)都會有一行對應的數據。了解並掌握 pg_statistic 的內容,對於深入理解查詢優化非常重要。

 

列的統計信息內容很豐富,但是目的都是讓優化器估算出,一個查詢條件,能夠過濾多少數據。

 

以下列舉了 pg_statistic 的重要字段:

 

 

對於 stakindN 字段中的統計方式,這里選擇3個最常見的進行說明:

 

1. STATISTIC_KIND_MCV

高頻值,在一個列中出現最頻繁的值。

高頻值統計在很多場景下都有價值,這里舉一個數據傾斜的 hash join 例子,如下代碼:

 

/* * ExecHashBuildSkewHash * * Set up for skew optimization if we can identify the most common values * (MCVs) of the outer relation's join key. We make a skew hash bucket * for the hash value of each MCV, up to the number of slots allowed * based on available memory. */static voidExecHashBuildSkewHash(HashJoinTable hashtable, Hash *node, int mcvsToUse){....}

 

hash join 場景下,我們需要盡可能的把 inner table 構建在內存中,但內存資源是有限的,因此我們需要做出一些選擇,什么內容優先放入內存中。如果外表有高頻值,那我們可以考慮把高頻值對應的內表信息優先放入到內存中,在實踐中,Greenplum 是單獨構建一個 skew hash table 與 main hash table 並存。

 

2. STATISTIC_KIND_HISTOGRAM

 

直方圖,使用等頻直方圖來描述一個列中的數據的分布。

直方圖主要用於數據分布不均勻的情況下,對按列過濾后能返回多少數據進行預估。

舉個例子,一個有3種產品的訂單表,商品 A 很熱銷,訂單量在90%,商品 B 一般,訂單量在9%,商品 C 只有1%,則該列的 NDV(Number of Distinct Value)值為3,如果一共有1000000條數據,在沒有直方圖統計的情況下,如果查詢商品 C 的訂單,優化器會預計要掃描1000000/3≈330000,因此可能選擇全表 scan,如果含有直方圖統計,優化器就知道實際上 C 商品可能就幾千條數據,因此會選擇走索引。當然這個例子很簡單,實際情況會復雜很多。

 

3. STATISTIC_KIND_CORRELATION

 

相關系數,記錄的是當前列未排序的數據分布和排序后的數據分布的相關性。

用於估算索引掃描代價的,統計值在-1到1,值越大,表示相關性越高,也就是使用索引掃描代價越低。

舉個例子,初始化如下2張表

 
create table t_correlation_asc (id int, number int) DISTRIBUTED BY (id);INSERT INTO t_correlation_asc SELECT 1, i FROM generate_series(1, 1000) AS i; create table t_correlation_desc (id int, number int) DISTRIBUTED BY (id);INSERT INTO t_correlation_desc SELECT 1, 1001-i FROM generate_series(1, 1000) AS i;
 

  

在查看表對應的統計信息,可以看出在 number 列,你按升序寫入1000個數,該列物理存儲的數據實際上就是按升序排序的,反過來降序寫入1000個數,由於順序是相反的,所以相關性是-1

 

correlation.png

 

四、例子

 

以下將會構造一個大小表 join 的場景,來說明統計信息的收集對於查詢計划的影響。

 

1. 初始化表結構和數據:

 
CREATE TABLE small_table (id int NOT NULL, bar text NOT NULL) DISTRIBUTED BY (id);INSERT INTO small_table SELECT i, 'test:' || i FROM generate_series(1, 10) AS i; CREATE TABLE big_table (id int NOT NULL, bar text NOT NULL) DISTRIBUTED BY (id);INSERT INTO big_table SELECT i, 'test:' || i FROM generate_series(1, 100000) AS i;

  

pg_class 中對應的數據如下:

 

small_table.png

 

big_table.png

 

2. 大小表 join

 

注意為了構造小表廣播的場景,這里關聯鍵需要選擇非分布鍵。

 

explain1.png

 

3. 給小表插入數據

 

這里給小表插入數據后,小表的數據量超過大表

 
INSERT INTO small_table SELECT i, 'test:' || i FROM generate_series(1, 200000) AS i;

  

在沒有 ANALYZE 的情況下,pg_class 中的數據沒有發生變化,因此查詢計划也沒有發生變化。

 

4. 收集統計信息

 

運行 ANALYZE 收集小表的統計信息,如下:

 

new_small_table.png 

 

在運行 join 語句,查詢計划發生變化:

 

explain2.png

 

結論:查詢優化器在收到新的統計信息之后,發現是2張數據量差不多的表進行 join,因此選擇重分布而不是小表廣播。

 


 

 

關注“騰訊雲大數據”公眾號,技術交流、最新活動、服務專享一站Get~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM