1. 程式人生 > >HAWQ技術解析(十一) —— 資料管理

HAWQ技術解析(十一) —— 資料管理

一、基本操作
1. INSERT

        在常用的增刪改查資料庫操作中,HAWQ僅支援INSERT和SELECT兩種,不支援UPDATE和DELETE,這主要是因為HDFS是一個只能追加資料而不能更新的檔案系統。SELECT語句最熟悉不過,它應該是資料庫中最常用的語句了,在下一篇“查詢優化”時再進一步討論。INSERT語句用於建立錶行,該命令需要表名和表中每個列的值。在HAWQ中,該命令有四種用法,其中三種是SQL中的常規用法,另一種是對標準SQL的擴充套件。
(1)指定列名與列值
        可以按任何順序指定列名,列值與列名一一對應。
insert into products (name, price, product_no) values ('Cheese', 9.99, 1);
(2)僅指定列值
        如果不指定列名,資料值列表的個數與順序必須與表中的列保持一致。
insert into products values (1, 'Cheese', 9.99);
(3)使用SELECT語句
        通常資料值是字元常量,但是也可以使用標量表達式或查詢語句。此時SELECT出的列表必須與插入表的列一致。
insert into products select * from tmp_products where price < 100;
insert into products (name, price, product_no) select * from tmp_products where price < 100;
        如果列有預設值,可以在插入語句中省略該列名而使用預設值。
insert into products (name, product_no) select name, product_no from tmp_products where price < 100;
(4)顯式一次插入多行
        這個SQL擴充套件與MySQL類似,一條INSERT語句中可以顯式指定多條需要插入的記錄。
db1=# create table t (a int);
CREATE TABLE
db1=# insert into t values (1),(2),(3);
INSERT 0 3
db1=# select * from t;
 a 
---
 1
 2
 3
(3 rows)
        如果需要快速插入大量資料,最好使用後面介紹的外部表或COPY命令,這些資料裝載機制比INSERT更有效。

2. 整理系統目錄表
(1)VACUUM
        對於資料庫中的物件,如表、檢視、函式等總是在不斷地執行新增、刪除、修改等操作,相應地會引起HAWQ系統目錄表的增刪改。因此對系統目錄良好的空間管理非常重要,這能夠給效能帶來大幅提高。當刪除一個數據庫物件時,並不立即釋放該條目在系統目錄表中所佔用的空間,而是在該條目上設定一個過期標誌。只有當執行VACUUM命令時,才會物理刪除那些已經標識為過期的資料條目並釋放空間。應該定期執行VACUUM命令移除過期行。VACUUM命令也可收集表級的統計資訊,如行數和頁數等。
db1=# -- 整理pg_class系統表
db1=# vacuum pg_class;
VACUUM
db1=# -- 整理並分析pg_class系統表
db1=# vacuum analyze pg_class;
VACUUM
db1=# -- 整理並分析pg_class系統表的指定列
db1=# vacuum analyze pg_class (relname,relam,relpages,reltuples,relkind,relnatts,relacl,reloptions);
VACUUM
(2)配置空餘空間對映
        過期行被儲存在名為free space map的結構中。free space map必須足夠大,能儲存資料庫中的所有過期行。VACUUM命令不能回收超過free space map以外過期行佔用的空間。注意,HAWQ中不推薦使用VACUUM FULL,因為對於大表,該操作可能造成不可接受的執行時間。
db1=# vacuum full pg_class;
NOTICE:  'VACUUM FULL' is not safe for large tables and has been known to yield unpredictable runtimes.
HINT:  Use 'VACUUM' instead.
VACUUM
        free space map的大小由以下伺服器配置引數所控制:
  • max_fsm_pages
  • max_fsm_relations
        max_fsm_pages(整數)設定free space map跟蹤的最大頁數。每個頁槽位佔用6位元組的共享記憶體。至少要設定為16 * max_fsm_relations。在初始安裝資料庫時,系統依照可用記憶體的數量設定該引數的預設值。設定該引數後需要重啟HAWQ使其生效。
[[email protected] ~]$ hawq config -s max_fsm_pages
GUC		: max_fsm_pages
Value		: 200000
        max_fsm_relations(整數)設定free space map跟蹤的最大表數。每個表槽位佔用7位元組左右的共享記憶體。預設值為1000。設定該引數後需要重啟HAWQ使其生效。
[[email protected] ~]$ hawq config -s max_fsm_relations
GUC		: max_fsm_relations
Value		: 1000
3. 其它操作
        與Oracle、MySQL等常用資料庫系統一樣,HAWQ支援MVCC併發訪問控制和非鎖定讀。支援ACCESS SHARE、ROW EXCLUSIVE、SHARE UPDATE EXCLUSIVE、SHARE、SHARE ROW EXCLUSIVE、ACCESS EXCLUSIVE六種鎖模式。並支援讀非提交、讀提交、可重複讀、序列化四種標準的事務隔離級別。因為資料倉庫應用中的ETL操作通常為一個獨立的後臺程式,幾乎沒有併發呼叫,而前臺的分析類應用大都是隻讀操作,所以這裡不展開討論HAWQ的併發控制與事務處理。

二、資料的裝載與解除安裝
        HAWQ既支援大資料量、多個檔案的高效能並行資料裝載解除安裝操作,又支援小資料量、單個檔案、非併發的資料匯入匯出。HAWQ可讀寫多種外部資料來源,包括本地文字檔案、HDFS或Web伺服器。
        在“用HAWQ輕鬆取代傳統資料倉庫(九) —— 外部資料”中詳細說明了PXF外部表,這裡將介紹使用另外一種協議——gpfdist的外部表。PXF外部錶針對HDFS上的檔案訪問,而gpfdist用於對本地檔案的並行訪問。gpfdist應用是一個HAWQ的並行檔案分佈程式。它是一個操作外部表的HTTP伺服器,使HAWQ的segment可以從多個檔案系統的外部表並行裝載資料。可以在多個不同的主機上執行gpfdist例項,並能夠並行使用它們。
        外部Web表提供了對動態資料地訪問。它支援使用HTTP協議從URL訪問資料,或者通過執行在segment上的指令碼輸出資料。
        hawq load應用使用一個YAML格式的控制檔案,自動完成資料裝載任務。
        HAWQ中的COPY SQL命令可在master主機上的文字檔案與HAWQ資料庫表之間轉移資料。

        所選擇的資料裝載方法依賴於資料來源的特性,如位置、資料量、格式、需要的轉換等。在最簡單的情況下,一條COPY命令就可將HAWQ主例項上的文字檔案裝載到表中。對於少量資料,這種方式不需要更多步驟,並提供了良好的效能。COPY命令在HAWQ master主機上的單個檔案與資料庫表之間拷貝資料。這種方式拷貝的資料量受限於檔案所在系統所允許的單一檔案最大位元組數。對於大資料集,更為有效的資料裝載方式是利用HAWQ的MPP架構,利用多個HAWQ segments並行裝載資料。該方式允許同時從多個檔案系統裝載資料,實現很高的資料傳輸速率。用gpfdist建立的外部表會使用所有HAWQ segment裝載或解除安裝資料,並且完全是並行操作的。
        無論使用哪種方法,裝載完資料都應執行ANALYZE。如果裝載了大量表資料,執行ANALYZE或VACUUM ANALYZE(只對系統目錄表)為查詢優化器更新表的統計資訊。使得當前統計資訊保證優化器做出最好的查詢計劃,避免由於資料的增長或缺失的統計資訊導致效能問題。

1. gpfdist
        gpfdist是HAWQ的提供的一種檔案伺服器,提供了良好的效能並且非常容易執行。gpfdist利用HAWQ系統中的所有segment讀寫外部表。
(1)並行性
        gp_external_max_segs伺服器配置引數控制可被單一gpfdist例項同時使用的虛擬段的數量,預設值為64。在master例項的hawq-site.xml檔案中設定此引數。
[[email protected] ~]$ hawq config -s gp_external_max_segs
GUC		: gp_external_max_segs
Value		: 64
        使用者可能需要設定虛擬段的數量,例如一些用於處理外部表資料檔案,一些執行其它的資料庫處理。hawq_rm_nvseg_perquery_perseg_limit和hawq_rm_nvseg_perquery_limit引數控制並行虛擬段的數量,它們限制叢集中一個gpfdist外部表上執行查詢時使用的最大虛擬段數。

(2)啟動與停止
        可以選擇在HAWQ master以外的其它機器上執行gpfdist,例如一個專門用於ETL處理的主機。使用gpfdist命令啟動gpfdist。該命令位於HAWQ master主機和每個segment主機的$GPHOME/bin目錄中。可以在當前目錄位置或者指定任意目錄啟動gpfdist,預設的埠是8080。下面是一些啟動gpfdist的例子。
  • 處理當前目錄中的檔案,使用預設8080埠。
    [[email protected] ~]$ gpfdist &
  • /home/gpadmin/load_data/是要處理的檔案目錄,8081是HTTP埠號,/home/gpadmin/log是訊息與錯誤日誌檔案,程序在後臺執行。
    [[email protected] ~]$ gpfdist -d /home/gpadmin/load_data/ -p 8081 -l /home/gpadmin/log &
  • 在同一個ETL主機上執行多個gpfdist例項,每個例項使用不同的目錄和埠。
    [[email protected] ~]$ gpfdist -d /home/gpadmin/load_data1/ -p 8081 -l /home/gpadmin/log1 &
    [[email protected] ~]$ gpfdist -d /home/gpadmin/load_data2/ -p 8082 -l /home/gpadmin/log2 &
        如果在啟動時報出類似沒有libapr-1.so.0或libyaml-0.so.2檔案的錯誤,則需要安裝相應的包。
yum install apr
yum install libyaml
        HAWQ沒有提供停止gpfdist的特殊命令,直接使用kill停止gpfdist程序。
[[email protected] ~]$ ps -ef |grep gpfdist |grep -v grep | awk '{print $2}'|xargs kill -9

(3)排錯
  • 虛擬段在執行時訪問gpfdist,因此需要保證HAWQ segment主機能訪問gpfdist例項。gpfdist實際上是一個Web伺服器,可以在HAWQ的每個主機(master和segment)上執行下面的命令測試連通性:
    [[email protected] ~]$ wget http://gpfdist_hostname:port/filename
  • CREATE EXTERNAL TABLE定義必須為gpfdist提供正確的主機名、埠號和檔名。
2. gpfdist外部表
(1)gpfdist協議
        在URI中使用gpfdist://協議引用一個執行的gpfdist例項。在外部資料檔案所在的主機上執行gpfdist命令。gpfdist自動解壓縮gzip(.gz)和bzip2(.bz2)檔案。可以使用萬用字元(*)或其它C語言風格的模式匹配多個需要讀取的檔案。指定的檔案應該位於啟動gpfdist例項時指定的目錄下。
        所有虛擬段並行訪問外部檔案,虛擬段的數量受gp_external_max_segments引數、gpfdist的位置列表長度,以及hawq_rm_nvseg_perquery_limit和hawq_rm_nvseg_perquery_perseg_limit引數影響。在CREATE EXTERNAL TABLE語句中使用多個gpfdist資料來源可擴充套件外部表掃描效能。

(2)建立gpfdist外部表

        為了建立一個gpfdist外部表,需要指定輸入檔案的格式和外部資料來源的位置。使用以下協議之一訪問外部表資料來源。一條CREATE EXTERNAL TABLE語句中使用的協議必須唯一,不能混用多個協議。
  • gpfdist:// —— 指定主機上的一個目錄,用於儲存外部資料檔案。HAWQ的所有segment可並行訪問該目錄下的檔案。
  • gpfdists:// —— gpfdist的安全版本。
        使用gpfdist外部表的步驟如下:
  1. 定義外部表。
  2. 啟動gpfdist檔案伺服器。
  3. 將資料檔案放置於外部表定義中指定的位置。
  4. 使用SQL命令查詢外部表。
        與PXF外部表一樣,HAWQ提供可讀與可寫兩種gpfdist外部表,但一個外部表不能既可讀又可寫。

(3)gpfdist外部表示例
        例1:單gpfdist例項外部表
        啟動gpfdist。
[[email protected] ~]$ gpfdist -p 8081 -d /home/gpadmin/staging -l /home/gpadmin/log &
        使用gpfdist協議建立只讀外部表example1,檔案以管道符(|)作為列分隔符。
db1=# create external table example1
db1=#         ( name text, date date, amount float4, category text, desc1 text )
db1=#     location ('gpfdist://hdp4:8081/*')
db1=#     format 'text' (delimiter '|');
CREATE EXTERNAL TABLE
        準備文字檔案資料。
[[email protected] unload_data1]$ cd /home/gpadmin/staging
[[email protected] staging]$ more a.txt 
aaa|2017-01-01|100.1|aaa|aaa
bbb|2017-01-02|100.2|bbb|bbb
[[email protected] staging]$ more b.txt 
aaa|2017-03-01|200.1|aaa|aaa
bbb|2017-03-02|200.2|bbb|bbb
        查詢外部表。
db1=# select * from example1;
 name |    date    | amount | category | desc1 
------+------------+--------+----------+-------
 aaa  | 2017-01-01 |  100.1 | aaa      | aaa
 bbb  | 2017-01-02 |  100.2 | bbb      | bbb
 aaa  | 2017-03-01 |  200.1 | aaa      | aaa
 bbb  | 2017-03-02 |  200.2 | bbb      | bbb
(4 rows)

        例2:多gpfdist例項外部表

        在hdp3和hdp4上分別啟動一個gpfdist例項。
[[email protected] ~]$ gpfdist -p 8081 -d /home/gpadmin/staging -l /home/gpadmin/log &
[[email protected] ~]$ gpfdist -p 8081 -d /home/gpadmin/staging -l /home/gpadmin/log &
        使用gpfdist協議建立只讀外部表example2,檔案以管道符(|)作為列分隔符,' '作為NULL。
db1=# create external table example2
db1-#         ( name text, date date, amount float4, category text, desc1 text )
db1-#     location ('gpfdist://hdp3:8081/*.txt', 'gpfdist://hdp4:8081/*.txt')
db1-#     format 'text' ( delimiter '|' null ' ') ;
CREATE EXTERNAL TABLE
        查詢外部表,因為gpfdist://hdp3:8081/*.txt不存在而報錯。
db1=# select * from example2;
ERROR:  http response code 404 from gpfdist (gpfdist://hdp3:8081/*.txt): HTTP/1.0 404 file not found (url.c:306)  (seg0 hdp4:40000 pid=53784) (dispatcher.c:1801)
db1=# 
        將外部檔案複製到hdp3的相關目錄下。
[[email protected] staging]$ scp *.txt hdp3://home/gpadmin/staging/
        再次查詢外部表,可以正確讀取全部外部檔案資料。
db1=# select * from example2;
 name |    date    | amount | category | desc1 
------+------------+--------+----------+-------
 aaa  | 2017-01-01 |  100.1 | aaa      | aaa
 bbb  | 2017-01-02 |  100.2 | bbb      | bbb
 aaa  | 2017-03-01 |  200.1 | aaa      | aaa
 bbb  | 2017-03-02 |  200.2 | bbb      | bbb
 aaa  | 2017-01-01 |  100.1 | aaa      | aaa
 bbb  | 2017-01-02 |  100.2 | bbb      | bbb
 aaa  | 2017-03-01 |  200.1 | aaa      | aaa
 bbb  | 2017-03-02 |  200.2 | bbb      | bbb
(8 rows)

        例3:帶有錯誤日誌的單gpfdist例項外部表

        預設在訪問外部表時只要遇到一行格式錯誤的資料,就會立即返回錯誤,並導致查詢失敗。下面的語句設定了SEGMENT REJECT LIMIT的值,只有當一個segment上的錯誤數大於等於5時,整個外部表操作才會失敗,並且不處理任何行。而當錯誤數小於5時,會將被拒絕的行寫入一個錯誤表errs,其它資料行還可以正常返回。
db1=# create external table example3
db1-#           ( name text, date date, amount float4, category text, desc1 text )
db1-#      location ('gpfdist://hdp3:8081/*.txt', 'gpfdist://hdp4:8081/*.txt')
db1-#      format 'text' ( delimiter '|' null ' ')
db1-#      log errors into errs segment reject limit 5;
NOTICE:  Error table "errs" does not exist. Auto generating an error table with the same name
CREATE EXTERNAL TABLE
db1=# \d errs
         Append-Only Table "public.errs"
  Column  |           Type           | Modifiers 
----------+--------------------------+-----------
 cmdtime  | timestamp with time zone | 
 relname  | text                     | 
 filename | text                     | 
 linenum  | integer                  | 
 bytenum  | integer                  | 
 errmsg   | text                     | 
 rawdata  | text                     | 
 rawbytes | bytea                    | 
Compression Type: None
Compression Level: 0
Block Size: 32768
Checksum: f
Distributed randomly

db1=# 
        準備一條格式錯誤的資料。
[[email protected] staging]$ more a.txt
aaa|2017-01-01|100.1|aaa|aaa
bbb|2017-01-02|100.2|bbb|bbb
bbb,2017-01-02,100.2,bbb,bbb
        查詢外部表,返回8條資料,錯誤資料進入了errs表。
db1=# select * from example3;
NOTICE:  Found 1 data formatting errors (1 or more input rows). Rejected related input data.
 name |    date    | amount | category | desc1 
------+------------+--------+----------+-------
 aaa  | 2017-01-01 |  100.1 | aaa      | aaa
 bbb  | 2017-01-02 |  100.2 | bbb      | bbb
 aaa  | 2017-03-01 |  200.1 | aaa      | aaa
 bbb  | 2017-03-02 |  200.2 | bbb      | bbb
 aaa  | 2017-01-01 |  100.1 | aaa      | aaa
 bbb  | 2017-01-02 |  100.2 | bbb      | bbb
 aaa  | 2017-03-01 |  200.1 | aaa      | aaa
 bbb  | 2017-03-02 |  200.2 | bbb      | bbb
(8 rows)

db1=# \x
Expanded display is on.
db1=# select * from errs;
-[ RECORD 1 ]-----------------------------------------------------
cmdtime  | 2017-04-05 15:23:19.579421+08
relname  | example3
filename | gpfdist://hdp4:8081/*.txt [/home/gpadmin/staging/a.txt]
linenum  | 3
bytenum  | 
errmsg   | missing data for column "date"
rawdata  | bbb,2017-01-02,100.2,bbb,bbb
rawbytes | 

db1=# 
        準備5條錯誤資料。
[[email protected] staging]$ more a.txt
aaa|2017-01-01|100.1|aaa|aaa
bbb|2017-01-02|100.2|bbb|bbb
b1,2017-01-02,100.2,bbb,bbb
b2,2017-01-02,100.2,bbb,bbb
b3,2017-01-02,100.2,bbb,bbb
b4,2017-01-02,100.2,bbb,bbb
b5,2017-01-02,100.2,bbb,bbb
        再次查詢外部表,因為達到了錯誤上限,整條語句失敗,沒有資料被返回。
db1=# select * from example3;
ERROR:  Segment reject limit reached. Aborting operation. Last error was: missing data for column "date"  (seg16 hdp3:40000 pid=350431)
DETAIL:  External table example3, line 7 of gpfdist://hdp4:8081/*.txt: "b5,2017-01-02,100.2,bbb,bbb"
db1=# 

        例4:gpfdist可寫外部表

        建立可寫外部表,並插入一條資料。
db1=# create writable external table example4 (name text, date date, amount float4, category text, desc1 text)
db1-#     location ('gpfdist://hdp4:8081/sales.out', 'gpfdist://hdp3:8081/sales.out')
db1-#     format 'text' ( delimiter '|' null ' ')
db1-#     distributed by (name);
CREATE EXTERNAL TABLE
db1=# insert into example4 values ('aaa','2017-01-01',100.1,'aaa','aaa');
INSERT 0 1
        結果只在hdp4上建立了檔案/home/gpadmin/staging/sales.out,而hdp3並沒有建立輸出檔案。
[[email protected] staging]$ more sales.out 
aaa|2017-01-01|100.1|aaa|aaa
        再次建立可寫外部表,將gpfdist位置調換,把hdp3放前面,並插入一條資料。
db1=# drop external table example4;
DROP EXTERNAL TABLE
db1=# create writable external table example4 (name text, date date, amount float4, category text, desc1 text)
db1-#     location ('gpfdist://hdp3:8081/sales.out', 'gpfdist://hdp4:8081/sales.out')
db1-#     format 'text' ( delimiter '|' null ' ')
db1-#     distributed by (name);
CREATE EXTERNAL TABLE
db1=# insert into example4 values ('aaa','2017-01-01',100.1,'aaa','aaa');
INSERT 0 1
        這次只在hdp3上建立了檔案/home/gpadmin/staging/sales.out。
[[email protected] staging]$ more sales.out 
aaa|2017-01-01|100.1|aaa|aaa
        在LOCATION子句中指定同一主機上的多個gpfdist例項,結果也是一樣的。可見,在可寫外部表上執行INSERT操作時,只在第一個gpfdist例項的位置上生成本地檔案資料。

3. 基於web的外部表
        外部表可以是基於檔案的或基於web的。
  • 基於檔案的外部表訪問靜態平面檔案。在查詢執行時資料是靜態的,資料可重複讀。
  • 基於web的外部表通過web伺服器的http協議或通過執行作業系統命令或指令碼,訪問動態資料來源。資料不可重複讀,因為在查詢執行時資料可能改變。
        CREATE EXTERNAL WEB TABLE語句建立一個web外部表。web外部表允許HAWQ將動態資料來源視作一個常規的資料庫表。因為web表資料可能在查詢執行時改變,所以資料是不可重複讀的。可以定義基於命令或基於URL的web外部表,但不能在一條建表命令中混用兩種定義。

(1)基於命令的web外部表

        用一個shell命令或指令碼的輸出定義基於命令的web表資料。在CREATE EXTERNAL WEB TABLE語句的EXECUTE子句指定需要執行的命令。外部表中的資料是命令執行時的資料。EXECUTE子句執行特定master或虛擬段上的shell命令或指令碼。指令碼必須是gpadmin使用者可執行的,並且位於所有master和segment主機的相同位置上,虛擬段並行執行命令。
        外部表定義中指定的命令從資料庫執行,資料庫不能從.bashrc或.profile獲取環境變數,因此需要在EXECUTE子句中設定環境變數。例如,下面的外部表執行一個HAWQ master主機上的密令:
CREATE EXTERNAL WEB TABLE output (output text)
EXECUTE 'PATH=/home/gpadmin/programs; export PATH; myprogram.sh'
    ON MASTER 
FORMAT 'TEXT';
        下面的命令定義一個web表,在五個虛擬段上執行一個名為get_log_data.sh指令碼檔案。
CREATE EXTERNAL WEB TABLE log_output (linenum int, message text) 
EXECUTE '/home/gpadmin/get_log_data.sh' ON 5 
FORMAT 'TEXT' (DELIMITER '|');
        資源管理器在執行時選取虛擬段。

(2)基於URL的Web外部表

        基於URL的web表使用HTTP協議從web伺服器訪問資料,web表資料是動態的。在LOCATION子句中使用http://指定檔案在web伺服器上的位置。web資料檔案必須在所有segment主機能夠訪問的web伺服器上。URL的數量對應訪問該web表時並行的最少虛擬段數量。下面的例子定義了一個從多個URL獲取資料的web表。
CREATE EXTERNAL WEB TABLE ext_expenses (
    name text, date date, amount float4, category text, description text) 
LOCATION ('http://hdp1/sales/file.csv',
          'http://hdp1/exec/file.csv',
          'http://hdp1/finance/file.csv',
          'http://hdp1/ops/file.csv',
          'http://hdp1/marketing/file.csv',
          'http://hdp1/eng/file.csv' 
      )
FORMAT 'CSV';

(3)基於Web的外部表示例

        例5:執行指令碼的可讀Web外部表
        建立外部表。
db1=# create external web table example5 (linenum int, message text) 
db1-# execute '/home/gpadmin/get_log_data.sh' on 5 
db1-# format 'text' (delimiter '|');
CREATE EXTERNAL TABLE
        HAWQ叢集中每臺主機的相同位置上都必須有同一個可執行的指令碼,否則查詢會報錯,如hdp1上沒有/home/gpadmin/get_log_data.sh檔案。
db1=# select * from example5;
ERROR:  external table example5 command ended with error. sh: /home/gpadmin/get_log_data.sh: No such file or directory  (seg0 hdp1:40000 pid=360600)
DETAIL:  Command: execute:/home/gpadmin/get_log_data.sh
        對該外部表的查詢會返回每個虛擬段輸出的並集,例如,get_log_data.sh指令碼內容如下:
#!/bin/bash
echo "1|aaa"
echo "2|bbb"
        則該表將返回10條(每個虛擬段兩條,五個虛擬段)資料:
db1=# select * from example5;
 linenum | message 
---------+---------
       1 | aaa
       2 | bbb
       1 | aaa
       2 | bbb
       1 | aaa
       2 | bbb
       1 | aaa
       2 | bbb
       1 | aaa
       2 | bbb
(10 rows)
        執行查詢時,資源管理器最少分配5個虛擬段。如果建表時指定的虛擬段數超過了允許的最大值,表仍然可以建立,但查詢時會報錯。
db1=# drop external web table example5;
DROP EXTERNAL TABLE
db1=# create external web table example5 (linenum int, message text) 
db1-# execute '/home/gpadmin/get_log_data.sh' on 100 
db1-# format 'text' (delimiter '|');
CREATE EXTERNAL TABLE
db1=# select * from example5;
ERROR:  failed to acquire resource from resource manager, minimum expected number of virtual segment 100 is more than maximum possible number 64 in queue pg_default (pquery.c:804)
        例6:執行指令碼的可寫web外部表
        建立外部表。
db1=# create writable external web table example6
db1-#         (name text, date date, amount float4, category text, desc1 text)
db1-#         execute 'PATH=/home/gpadmin/programs; export PATH; myprogram1.sh' on 6
db1-#         format 'text' (delimiter '|')
db1-#         distributed randomly;
CREATE EXTERNAL TABLE
        myprogram1.sh的內容如下:
#!/bin/bash
while read line
do
    echo "File:${line}" >> /home/gpadmin/programs/a.txt
done
        向外部表中插入資料。
db1=# insert into example6 values ('aaa','2017-01-01',100.1,'aaa','aaa');
INSERT 0 1
db1=# insert into example6 values ('bbb','2017-02-01',200.1,'bbb','');
INSERT 0 1
        插入的資料通過管道輸出給myprogram1.sh並執行,可以看到插入的資料被寫入了a.txt檔案。與可讀表不同,該檔案只在一個HAWQ主機上生成,並且每次插入資料只生成一行。
[[email protected] programs]$ more  /home/gpadmin/programs/a.txt
File:aaa|2017-01-01|100.1|aaa|aaa
File:bbb|2017-02-01|200.1|bbb|
5. 使用外部表裝載資料
        使用INSERT INTO target_table SELECT ... FROM source_external_table命令裝載資料。例如:
CREATE TABLE expenses_travel (LIKE ext_expenses);
INSERT INTO expenses_travel 
SELECT * FROM ext_expenses WHERE category='travel';
        也可以在建立一個新表的同時裝載資料:
CREATE TABLE expenses AS SELECT * FROM ext_expenses;

6. 外部表錯誤處理

        可讀外部表通常被用於選擇資料裝載到普通的HAWQ資料庫表中。使用CREATE TABLE AS SELECT或INSERT INTO命令查詢外部表資料。預設時,如果資料包含錯誤,則整條命令失敗,沒有資料裝載到目標資料庫表中。
        SEGMENT REJECT LIMIT子句允許隔離外部表中格式錯誤的資料,並繼續裝載格式正確的行。使用SEGMENT REJECT LIMIT設定一個錯誤閾值,指定拒絕的資料行數(預設)或一個佔總行數的百分比(1-100)。
        如果錯誤行數達到了SEGMENT REJECT LIMIT的值,整個外部表操作失敗,沒有資料行被處理。限制的錯誤行數是相對於一個虛擬段的,不是整個操作的。如果錯誤行數沒有達到SEGMENT REJECT LIMIT值,操作處理所有正確的行,丟棄錯誤行,或者可選地將格式錯誤的行寫入日誌表。LOG ERRORS子句允許儲存錯誤行以備後續檢查。
        設定SEGMENT REJECT LIMIT會使HAWQ以單行錯誤隔離模式掃描外部資料。當外部資料行出現多餘屬性、缺少屬性、資料型別錯誤、無效的客戶端編碼序列等格式錯誤時,單行錯誤隔離模式將錯誤行丟棄或寫入日誌表。HAWQ不檢查約束錯誤,但可以在查詢外部表時過濾約束錯誤。例如,消除重複鍵值錯誤:
INSERT INTO table_with_pkeys 
SELECT DISTINCT * FROM external_table;

(1)使用單行錯誤隔離定義外部表

        下面的例子在HAWQ表中記錄錯誤記錄,並設定錯誤行閾值為10。
db1=# create external table ext_expenses ( name text, date date, amount float4, category text, desc1 text )
db1-#    location ('gpfdist://hdp3:8081/*', 'gpfdist://hdp4:8081/*')
db1-#    format 'text' (delimiter '|')
db1-#    log errors into errs segment reject limit 10 rows;
CREATE EXTERNAL TABLE

(2)標識無效的CSV檔案資料

        如果一個CSV檔案包含無效格式,錯誤日誌表的rawdata欄位可能包含多行。例如,某欄位少了一個閉合的引號,後面所有的換行符都被認為是資料中內嵌的換行符。當這種情況發生時,HAWQ在一行資料達到64K時停止解析,並將此64K資料作為單行寫入錯誤日誌表,然後重置引號標記,繼續讀取資料。如果這種情況在處理裝載時發生三次,載入檔案被認為是無效的,整個裝載失敗,錯誤資訊為“rejected N or more rows”。

(3)表間遷移資料

        可以使用CREATE TABLE AS或INSERT...SELECT語句將外部表或web外部表的資料裝載到其它非外部表中,資料將根據外部表或web外部表的定義並行裝載。如果一個外部表或web外部表資料來源有錯誤,依賴於使用的錯誤隔離模式,有以下兩種處理方式:
  • 表沒有設定錯誤隔離模式:讀取該表的任何操作都會失敗。沒有設定錯誤隔離模式的外部表或web外部表上的操作將整體成功或失敗。
  • 表設定了錯誤隔離模式:除了發生錯誤的行,其它資料將被裝載(依賴於REJECT_LIMIT的配置)。
7. 使用hawq load裝載資料
        HAWQ的hawq load應用程式使用可讀外部表和HAWQ並行檔案系統(gpfdist或gpfdists)裝載資料。它並行處理基於檔案建立的外部表,允許使用者在單一配置檔案中配置資料格式、外部表定義,以及gpfdist或gpfdists的設定。

(1)確認建立了執行hawq load的環境。

        它需要依賴某些HAWQ安裝中的檔案,如gpfdist和Python,還需要通過網路訪問所有HAWQ segment主機。

(2)建立控制檔案。

        hawq load的控制檔案是一個YAML(Yet Another Markup Language)格式的檔案,在其中指定HAWQ連線資訊,gpfdist配置資訊,外部表選項、資料格式等。例如下面是一個名為my_load.yml的控制檔案內容:

---
VERSION: 1.0.0.1
DATABASE: db1
USER: gpadmin
HOST: hdp3
PORT: 5432
GPLOAD:
   INPUT:
    - SOURCE:
         LOCAL_HOSTNAME:
           - hdp4
         PORT: 8081
         FILE: 
           - /home/gpadmin/staging/*.txt
    - COLUMNS:
           - name: text
           - date: date
           - amount: float4
           - category: text
           - desc1: text           
    - FORMAT: text
    - DELIMITER: '|'
    - ERROR_LIMIT: 25
    - ERROR_TABLE: errlog
   OUTPUT:
    - TABLE: t1
    - MODE: INSERT
   SQL:
    - BEFORE: "INSERT INTO audit VALUES('start', current_timestamp)"
    - AFTER: "INSERT INTO audit VALUES('end', current_timestamp)"
    注意:
  • hawq load控制檔案使用YAML 1.1文件格式,為了定義HAWQ資料裝載的各種步驟,它定義了自己的schema。控制檔案必須是一個有效的YAML文件。hawq load程式按順序處理控制檔案文件,並使用空格識別文件中各段之間的層次關係,因此空格的使用是非常重要的。不要使用TAB符代替空格,YAML文件中不要出現TAB符。
  • LOCAL_HOSTNAME指定執行hawq load的本地主機名或IP地址。如果機器配置了多塊網絡卡,可以為每塊網絡卡指定一個主機名,允許同時使用多塊網絡卡傳輸資料。比如hdp4上配置了兩塊網絡卡,可以如下配置LOCAL_HOSTNAME:
    LOCAL_HOSTNAME:
     - hdp4-1
     - hdp4-2
(3)hawq load示例
        準備本地檔案資料。
[[email protected] staging]$ more a.txt 
aaa|2017-01-01|100.1|aaa|aaa
bbb|2017-01-02|100.2|bbb|bbb
[[email protected] staging]$ more b.txt 
aaa|2017-03-01|200.1|aaa|aaa
bbb|2017-03-02|200.2|bbb|bbb
        建立目標表和audit表。
db1=# create table t1 ( name text, date date, amount float4, category text, desc1 text );
CREATE TABLE
db1=# create table audit(flag varchar(10),st timestamp); 
CREATE TABLE
        執行hawq load。
[[email protected] ~]$ hawq load -f my_load.yml
2017-04-05 16:41:44|INFO|gpload session started 2017-04-05 16:41:44
2017-04-05 16:41:44|INFO|setting schema 'public' for table 't1'
2017-04-05 16:41:44|INFO|started gpfdist -p 8081 -P 8082 -f "/home/gpadmin/staging/*.txt" -t 30
2017-04-05 16:41:49|INFO|running time: 5.63 seconds
2017-04-05 16:41:49|INFO|rows Inserted          = 4
2017-04-05 16:41:49|INFO|rows Updated           = 0
2017-04-05 16:41:49|INFO|data formatting errors = 0
2017-04-05 16:41:49|INFO|gpload succeeded
[[email protected] ~]$
        查詢目標表和audit表。
db1=# select * from t1;
 name |    date    | amount | category | desc1 
------+------------+--------+----------+-------
 aaa  | 2017-01-01 |  100.1 | aaa      | aaa
 bbb  | 2017-01-02 |  100.2 | bbb      | bbb
 aaa  | 2017-03-01 |  200.1 | aaa      | aaa
 bbb  | 2017-03-02 |  200.2 | bbb      | bbb
(4 rows)

db1=# select * from audit;
 flag  |             st             
-------+----------------------------
 start | 2017-04-05 16:41:44.736296
 end   | 2017-04-05 16:41:49.60153
(2 rows)

8. 使用COPY裝載、解除安裝資料
        COPY是HAWQ的SQL命令,它在標準輸入和HAWQ表之間互拷資料。COPY FROM命令將本地檔案追加到資料表中,而COPY TO命令將資料表中的資料覆蓋寫入本地檔案。COPY命令是非並行的,資料在HAWQ master例項上以單程序處理,因此只推薦對非常小的資料檔案使用COPY命令。本地檔案必須在master主機上,預設的檔案格式是逗號分隔的CSV文字檔案。HAWQ使用客戶端與master伺服器之間的連線,從STDIN或STDOUT拷貝資料。
[[email protected] ~]$ psql -h hdp3 -d db1
psql (8.2.15)
Type "help" for help.

db1=# create table t2 (like t1);
NOTICE:  Table doesn't have 'distributed by' clause, defaulting to distribution columns from LIKE table
CREATE TABLE
db1=# copy t2 from '/home/gpadmin/staging/a.txt' with delimiter '|';
COPY 2
db1=# select * from t2;
 name |    date    | amount | category | desc1 
------+------------+--------+----------+-------
 aaa  | 2017-01-01 |  100.1 | aaa      | aaa
 bbb  | 2017-01-02 |  100.2 | bbb      | bbb
(2 rows)
        將表資料解除安裝到master的本地檔案中,如果檔案不存在則建立檔案,否則會用解除安裝資料覆蓋檔案原來的內容。
db1=# copy (select * from t2) to '/home/gpadmin/staging/c.txt' with delimiter '|';
COPY 2

[[email protected] staging]$ more /home/gpadmin/staging/c.txt 
bbb|2017-01-02|100.2|bbb|bbb
aaa|2017-01-01|100.1|aaa|aaa

(1)以單行錯誤隔離模式執行COPY。

        預設時,COPY在遇到第一個錯誤時就停止執行。如果資料含有錯誤,操作失敗,沒有資料被裝載。如果以單行錯誤隔離模式執行COPY,HAWQ跳過含有錯誤格式的行,裝載具有正確格式的行。如果資料違反了NOT NULL或CHECK等約束條件,操作仍然是‘all-or-nothing’輸入模式,整個操作失敗,沒有資料被裝載。
[[email protected] staging]$ more a.txt
aaa|2017-01-01|100.1|aaa|aaa
bbb|2017-01-02|100.2|bbb|bbb
        向表拷貝本地檔案資料,錯誤日誌表中新增一條資料。
db1=# create table t3 ( name text not null, date date, amount float4, category text, desc1 text );
CREATE TABLE
db1=# copy t3 from '/home/gpadmin/staging/a.txt'
db1-#    with delimiter '|' log errors into errtable
db1-#    segment reject limit 5 rows;
NOTICE:  Error table "errtable" does not exist. Auto generating an error table with the same name
WARNING:  The error table was created in the same transaction as this operation. It will get dropped if transaction rolls back even if bad rows are present
HINT:  To avoid this create the error table ahead of time using: CREATE TABLE <name> (cmdtime timestamp with time zone, relname text, filename text, linenum integer, bytenum integer, errmsg text, rawdata text, rawbytes bytea)
NOTICE:  Dropping the auto-generated unused error table
HINT:  Use KEEP in LOG INTO clause to force keeping the error table alive
COPY 2
db1=# select * from t3;
 name |    date    | amount | category | desc1 
------+------------+--------+----------+-------
 bbb  | 2017-01-02 |  100.2 | bbb      | bbb
 aaa  | 2017-01-01 |  100.1 | aaa      | aaa
(2 rows)
        修改檔案,製造一行格式錯誤的資料。   
[[email protected] staging]$ more a.txt
aaa,2017-01-01,100.1,aaa,aaa
bbb|2017-01-02|100.2|bbb|bbb
        再次拷貝資料。與解除安裝不同,裝載會向表中追加資料。
db1=# copy t3 from '/home/gpadmin/staging/a.txt'
db1-#    with delimiter '|' log errors into errtable
db1-#    segment reject limit 5 rows;
NOTICE:  Error table "errtable" does not exist. Auto generating an error table with the same name
WARNING:  The error table was created in the same transaction as this operation. It will get dropped if transaction rolls back even if bad rows are present
HINT:  To avoid this create the error table ahead of time using: CREATE TABLE <name> (cmdtime timestamp with time zone, relname text, filename text, linenum integer, bytenum integer, errmsg text, rawdata text, rawbytes bytea)
NOTICE:  Found 1 data formatting errors (1 or more input rows). Errors logged into error table "errtable"
COPY 1
db1=# select * from t3;
 name |    date    | amount | category | desc1 
------+------------+--------+----------+-------
 bbb  | 2017-01-02 |  100.2 | bbb      | bbb
 bbb  | 2017-01-02 |  100.2 | bbb      | bbb
 aaa  | 2017-01-01 |  100.1 | aaa      | aaa
(3 rows)

db1=# \x
Expanded display is on.
db1=# select * from errtable;
-[ RECORD 1 ]----------------------------
cmdtime  | 2017-04-05 16:56:02.402161+08
relname  | t3
filename | /home/gpadmin/staging/a.txt
linenum  | 1
bytenum  | 
errmsg   | missing data for column "date"
rawdata  | aaa,2017-01-01,100.1,aaa,aaa
rawbytes | 

db1=#
        再次修改檔案,將name欄位對應的資料置空,因為該欄位定義為NOT NULL,所以違反約束,沒有資料被拷貝。   
[[email protected] staging]$ more a.txt
|2017-01-01|100.1|aaa|aaa
bbb|2017-01-02|100.2|bbb|bbb
  
db1=# truncate table t3;
TRUNCATE TABLE
db1=# copy t3 from '/home/gpadmin/staging/a.txt'
   with delimiter '|' null as  '' log errors into errtable
   segment reject limit 5 rows;
ERROR:  null value in column "name" violates not-null constraint  (seg5 hdp1:40000 pid=370883)
CONTEXT:  COPY t3, line 1: "|2017-01-01|100.1|aaa|aaa"
db1=# select * from t3;
 name | date | amount | category | desc1 
------+------+--------+----------+-------
(0 rows)

9. 解除安裝資料
        一個可寫外部表允許使用者從其它資料庫表選擇資料行並輸出到檔案、命名管道、應用或MapReduce。如前面的例4和例6所示,可以定義基於gpfdist或基於web的可寫外部表。
        對於使用gpfdist協議的外部表,HAWQ segment將它們的資料傳送給gpfdist,gpfdist將資料寫入命名檔案中。gpfdist必須執行在HAWQ segment能夠在網路上訪問的主機上。gpfdist指向一個輸出主機上的檔案位置,將從HAWQ segment接收到的資料寫入檔案。
        一個可寫web外部表的資料作為資料流傳送給應用。例如,從HAWQ解除安裝資料併發送給一個連線其它資料庫的應用或向別處裝載資料的ETL工具。可寫web外部表使用EXECUTE子句指定一個執行在segment主機上的shell命令、指令碼或應用,接收輸入資料流。
        可以選擇為可寫外部表宣告分佈策略。預設時,可寫外部表使用隨機分佈策略。如果要匯出的源表是雜湊分佈的,為外部表定義相同的分佈鍵列會提升資料解除安裝效能,因為這消除了資料行在內部網際網路絡上的移動。如果解除安裝一個特定表的資料,可以使用LIKE子句拷貝源表的列定義與分佈策略。
        下面是一個gpfdist外部表的例子:
db1=# create writable external table unload_expenses
db1-# ( like t1 )
db1-# location ('gpfdist://hdp3:8081/expenses1.out',
db1(# 'gpfdist://hdp4:8081/expenses2.out')
db1-# format 'text' (delimiter ',');
NOTICE:  Table doesn't have 'distributed by' clause, defaulting to distribution columns from LIKE table
CREATE EXTERNAL TABLE
        可寫外部表只允許INSERT操作。如果執行解除安裝的使用者不是外部表的屬主或超級使用者,必須授予對外部表的INSERT許可權。例如:
GRANT INSERT ON unload_expenses TO admin;
        與例4不同,INSERT INTO 外部表 SELECT ... 語句中,外部表的輸出檔案只能在一個主機上,否則會報錯。
db1=# insert into unload_expenses select * from t1;
ERROR:  External table has more URLs then available primary segments that can write into them  (seg0 hdp1:40000 pid=387379)
db1=# drop external table unload_expenses;
DROP EXTERNAL TABLE
db1=# create writable external table unload_expenses
db1-# ( like t1 )
db1-# location ('gpfdist://hdp3:8081/expenses1.out')
db1-# format 'text' (delimiter ',');
NOTICE:  Table doesn't have 'distributed by' clause, defaulting to distribution columns from LIKE table
CREATE EXTERNAL TABLE
db1=# insert into unload_expenses select * from t1;
INSERT 0 4
        檢視匯出的資料。
[[email protected] staging]$ more expenses1.out 
aaa,2017-01-01,100.1,aaa,aaa
bbb,2017-01-02,100.2,bbb,bbb
aaa,2017-03-01,200.1,aaa,aaa
bbb,2017-03-02,200.2,bbb,bbb
[[email protected] staging]$
        如上面的例6所示,也可以定義一個可寫的外部web表,傳送資料行到指令碼或應用。指令碼檔案必須接收輸入流,而且必須存在於所有HAWQ segment的主機的相同位置上,並可以被gpadmin使用者執行。HAWQ系統中的所有segment都執行指令碼,無論segment是否有需要處理的輸出行。
        允許外部表執行作業系統命令或指令碼會帶來相應的安全風險。為了在可寫外部web表定義中禁用EXECUTE,可在HAWQ master的hawq-site.xml檔案中設定gp_external_enable_exec伺服器配置引數為off。
gp_external_enable_exec = off
        正如前面說明COPY命令時所看到的,COPY TO命令也可以用來解除安裝資料。它使用HAWQ master主機上的單一程序,從表中資料拷貝到HAWQ master主機上的一個檔案(或標準輸入)中。COPY TO命令重寫整個檔案,而不是追加記錄。

10. hawq register
        該命令將HDFS上的Parquet表資料裝載並註冊到對應的HAWQ表中。hawq register的使用場景好像很有限,因為它只能註冊HAWQ或Hive已經生成的Parquet表文件。關於該命令的使用可參考“Registering Files into HAWQ Internal Tables”。

11. 格式化資料檔案
        當使用HAWQ工具裝載解除安裝資料時,必須指定資料的格式。CREATE EXTERNAL TABLE、hawq load和COPY都包含指定資料格式的子句。資料可以是固定分隔符的文字或逗號分隔值(CSV)格式。外部資料必須是HAWQ可以正確讀取的格式。

(1)行格式

        HAWQ需要資料行以換行符(LF,Line feed,ASCII值0x0A)、回車符(CR,Carriage return,ASCII值0x0D)或回車換行符(CR+LF,0x0D 0x0A)作為行分隔符。LF是類UNIX作業系統中標準的換行符。而Windows或Mac OS X使用CR或CR+LF。所有這些表示一個新行的特殊符號都被HAWQ作為行分隔符所支援。

(2)列格式

        文字檔案和CSV檔案預設的列分隔符是分別是TAB(ASCII值為0x09)和逗號(ASCII值為0x2C)。在定義資料格式時,可以在CREATE EXTERNAL TABLE或COPY命令的DELIMITER子句,或者hawq load的控制檔案中,宣告一個單字元作為列分隔符。分隔符必須出現在欄位值之間,不要在一行的開頭或結尾放置分隔符。例如,下面使用管道符(|)作為列分隔符:
data value 1|data value 2|data value 3
        下面的建表命令顯示以管道符作為列分隔符:
=# CREATE EXTERNAL TABLE ext_table (name text, date date)
LOCATION ('gpfdist://host:port/filename.txt)
FORMAT 'TEXT' (DELIMITER '|');

(3)表示空值

        空值(NULL)表示一列中的未知資料。可以指定資料檔案中的一個字串表示空值。文字檔案中表示空值的預設字串為\N,CSV檔案中表示空值的預設字串為不帶引號的空串(兩個連續的逗號)。定義資料格式時,可以在CREATE EXTERNAL TABLE、COPY命令的NULL子句,或者hawq load的控制檔案中,宣告其它字串表示空值。例如,如果不想區分空值與空串,就可以指定空串表示NULL。在使用HAWQ裝載工具時,任何與宣告的代表NULL的字串相匹配的資料項都被認為是空值。

(4)轉義

        列分隔符與行分隔符在資料檔案中具有特殊含義。如果實際資料中也含有這個符號,必須對這些符號進行轉義,以使HAWQ將它們作為普通資料而不是列或行的分隔符。文字檔案預設的轉義符為一個反斜槓(\),CSV檔案預設的轉義符為一個雙引號(")。
        文字檔案轉義
        可以在CREATE EXTERNAL TABLE、COPY的ESCAPE子句,或者hawq load的控制檔案中指定轉義符。例如,假設有以下三個欄位的資料:
  • backslash = \
  • vertical bar = |
  • exclamation point = !
        假設指定管道符(|)為列分隔符,反斜槓(\)為轉義符。則對應的資料行格式如下:
backslash = \\ | vertical bar = \| | exclamation point = !
        可以對八進位制或十六進位制序列應用轉義符。在裝載進HAWQ時,轉義後的值就是八進位制或十六進位制的ASCII碼所表示的字元。例如,取址符(&)可以使用十六進位制的(\0x26)或八進位制的(\046)表示。
        如果要在CREATE EXTERNAL TABLE、COPY命令的ESCAPE子句,或者hawq load的控制檔案中禁用轉義,可如下設定:
ESCAPE 'OFF'
        該設定常用於輸入資料中包含很多反斜槓(如web日誌資料)的情況。

        CSV檔案轉義
        可以在CREATE EXTERNAL TABLE、COPY的ESCAPE子句,或者hawq load的控制檔案中指定轉義符。例如,假設有以下三個欄位的資料:
  • Free trip to A,B
  • 5.89
  • Special rate "1.79"
        假設指定逗號(,)為列分隔符,一個雙引號(")為轉義符。則資料行格式如下:
"Free trip to A,B","5.89","Special rate ""1.79"""
        將欄位值置於雙引號中能保留字串中頭尾的空格。

(5)字元編碼

        在將一個Windows作業系統上生成的資料檔案裝載到HAWQ前,先使用dos2unix系統命令去除只有Windows使用的字元,如刪除檔案中的CR('\x0d')。

(6)匯入匯出固定寬度資料

        HAWQ的函式fixedwith_in和fixedwidth_out支援固定寬度的資料格式。這些函式的定義儲存在$GPHOME/share/postgresql/cdb_external_extensions.sql檔案中。下面的例子聲明瞭一個自定義格式,然後呼叫fixedwidth_in函式指定為固定寬度的資料格式。
db1=# create readable external table students (
db1(#   name varchar(5), address varchar(10), age int)
db1-# location ('gpfdist://hdp4:8081/students.txt')
db1-# format 'custom' (formatter=fixedwidth_in, name='5', address='10', age='4');
CREATE EXTERNAL TABLE
db1=# select * from students;
 name  |  address   | age 
-------+------------+-----
 abcde | 1234567890 |  40
(1 row)
        students.txt檔案內容如下:
[[email protected] unload_data1]$ more students.txt 
abcde12345678900040
        檔案中一行記錄的位元組必須與建表語句中欄位位元組數的和一致,如上例中一行必須嚴格為19位元組,否則讀取檔案時會報錯。再看一個含有中文的例子。
[[email protected] unload_data1]$ echo $LANG
zh_CN.UTF-8
[[email protected] unload_data1]$ more students.txt 
中文中文中0040
        作業系統和資料庫的字符集都是UTF8,一箇中文佔用三個位元組,記錄一共19位元組,滿足讀取條件。
db1=# select * from students;
 name | address | age 
------+---------+-----
 中   | 中文中  |  40
(1 row)
        name欄位5位元組,address欄位10位元組,理論上這兩個欄位都應該含有不完整的字元,但從查詢結果看到,HAWQ在這裡做了一些處理,name欄位讀取了一個完整的中文,address欄位讀取了三個完整的字元。而中間按位元組分裂的中文字元被不可見字元所取代。
db1=# select char_length(name),octet_length(name),substr(name,1,1),substr(name,2,1) from students;
 char_length | octet_length | substr | substr 
-------------+--------------+--------+--------
           2 |            5 | 中     | 
(1 row)

db1=# select char_length(address),octet_length(address),substr(address,1,1),substr(address,2,1) from students;
 char_length | octet_length | substr | substr 
-------------+--------------+--------+--------
           4 |           10 |        | 中
(1 row)
        以下選項指定如何讀取固定寬度資料檔案。
  • 讀取全部資料。裝載固定寬度資料一行中的所有欄位,並按它們的物理順序進行裝載。必須指定欄位長度,不能指定起始於終止位置。固定寬度引數中欄位名的順序必須與CREATE TABLE命令中的順序相匹配。
  • 設定空格與NULL特性。預設時尾部空格被擷取。為了保留尾部空格,使用preserve_blanks=on選項。使用null='null_string_value'選項指定代表NULL的字串。
  • 如果指定了preserve_blanks=on,也必須定義代表NULL值的字串。否則會報ERROR:  A null_value was not defined. When preserve_blanks is on, a null_value
  • 如果指定了preserve_blanks=off,沒有定義NULL,並且一個欄位只包含空格,HAWQ向表中寫一個null。如果定義了NULL,HAWQ向表中寫一個空串。
  • 使用line_delim='line_ending'引數指定行尾字元。下面的例子覆蓋大多數情況。‘E’表示轉義,就是說如果記錄正文中含有line_delim,需要進行轉義。
    line_delim=E'\n'
    line_delim=E'\r'
    line_delim=E'\r\n'
    line_delim='abc'
三、資料庫統計
1. 概述

        統計資訊指的是資料庫中所儲存資料的元資訊描述。查詢優化器需要依據最新的統計資訊,為查詢生成最佳的執行計劃。例如,如果查詢連線了兩個表,一個表必須被廣播到所有段,那麼優化器會選擇廣播其中的小表,使網路流量最小化。
        ANALYZE命令計算優化器所需的統計資訊,並將結果儲存到系統目錄中。有三種方式啟動分析操作:
  • 直接執行ANALYZE命令。
  • 在資料庫外執行analyzedb命令列應用程式。
  • 在執行DML操作的表上沒有統計資訊,或者DML操作影響的行數超過了指定的閾值時,自動執行分析操作。
        計算統計資訊會消耗時間和資源,因此HAWQ會在大表上進行取樣,通過計算部分資料,產生統計資訊的估算。在大多數情況下,預設設定能夠提供生成正確查詢執行計劃的資訊。如果產生的統計不能生成優化的查詢執行計劃,管理員可以調整配置引數,通過增加樣本資料量,產生更加精確的統計資訊。統計資訊越精確,所消耗的CPU和記憶體資源越多,因此可能由於資源的限制,無法生成更好的計劃。此時就需要檢視執行計劃並測試查詢效能,目標是要通過增加的統計成本達到更好的查詢效能。

2. 系統統計
(1)表大小
        查詢優化器使用查詢必須處理的資料行數和必須訪問的磁碟頁數等統計資訊,尋找查詢所需的最小的磁碟I/O和網路流量的執行計劃。用於估算行數和頁數的資料分別儲存在pg_class系統表的reltuples和relpages列中,其中的值是最後執行VACUUM或ANALYZE命令時生成的資料。對於預設的AO(Append Only)表,系統目錄中的tuples數是最近的值,因此reltuples統計是精確值而不是估算值,但relpages值是AO資料塊的估算值。如果reltuples列的值與SELECT COUNT(*)的返回值差很多,應該執行分析更新統計資訊。

(2)pg_statistic系統表與pg_stats檢視

        pg_statistic系統表儲存每個資料庫表上最後執行ANALYZE操作的結果。每個表列有一行記錄,它具有以下欄位:
  • starelid:列所屬的表的物件ID。
  • staatnum:所描述列在表中的編號,從1開始。
  • stanullfrac;列中空值佔比。
  • stawidth:非空資料項的平均寬度,單位是位元組。
  • stadistinct:列中不同非空資料值的個數。
  • stakindN:用於表示後面number、values所表示的資料用途,被用於生成pg_stats。如1則表示是MCV(Most Common Values)的值;2表示直方圖(histogram)的值;3表示相關性(correlation)的值等。kind的取值範圍:1~99,核心佔用;100~199,PostGIS佔用;200~299,ESRI ST_Geometry幾何系統佔用;300~9999,公共佔用。
  • staopN:用於表示該統計值支援的操作,如’=’或’<’等。
  • stanumbersN:如果是MCV型別(即kind=1),那麼這裡即是下面對應的stavaluesN出現的概率值,即MCF。
  • stavaluesN:anyarray型別的資料,核心特殊型別,不可更改。是統計資訊的值部分,與kind對應。如kind=2的時候,則這裡的值表示直方圖。
        pg_statistic表將不同的統計型別分為四類,分別用四個欄位表示。而pg_stats檢視以一種更友好的方式表示pg_statistic的內容,其定義如下:
 SELECT n.nspname AS schemaname, c.relname AS tablename, a.attname, s.stanullfrac AS null_frac, s.stawidth AS avg_width, s.stadistinct AS n_distinct, 
        CASE 1
            WHEN s.stakind1 THEN s.stavalues1
            WHEN s.stakind2 THEN s.stavalues2
            WHEN s.stakind3 THEN s.stavalues3
            WHEN s.stakind4 THEN s.stavalues4
            ELSE NULL::anyarray
        END AS most_common_vals, 
        CASE 1
            WHEN s.stakind1 THEN s.stanumbers1
            WHEN s.stakind2 THEN s.stanumbers2
            WHEN s.stakind3 THEN s.stanumbers3
            WHEN s.stakind4 THEN s.stanumbers4
            ELSE NULL::real[]
        END AS most_common_freqs, 
        CASE 2
            WHEN s.stakind1 THEN s.stavalues1
            WHEN s.stakind2 THEN s.stavalues2
            WHEN s.stakind3 THEN s.stavalues3
            WHEN s.stakind4 THEN s.stavalues4
            ELSE NULL::anyarray
        END AS histogram_bounds, 
        CASE 3
            WHEN s.stakind1 THEN s.stanumbers1[1]
            WHEN s.stakind2 THEN s.stanumbers2[1]
            WHEN s.stakind3 THEN s.stanumbers3[1]
            WHEN s.stakind4 THEN s.stanumbers4[1]
            ELSE NULL::real
        END AS correlation
   FROM pg_statistic s
   JOIN pg_class c ON c.oid = s.starelid
   JOIN pg_attribute a ON c.oid = a.attrelid AND a.attnum = s.staattnum
   LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
  WHERE has_table_privilege(c.oid, 'select'::text);
        新建的表沒有統計資訊。

(3)取樣

        在為大表計算統計資訊時,HAWQ通過對基表取樣資料的方式建立一個小表。如果基表是分割槽表,從全部分割槽中取樣。樣本表中的行數取決於由gp_analyze_relative_error系統配置引數指定的最大可接受錯誤數。該引數的預設值是0.25(25%)。通常該值已經足夠生成正確的查詢計劃。如果ANALYZE不能產生好的表列估算,可以通過調低該引數值,增加取樣的資料量。注意,降低該值可能導致大量的取樣資料,並明顯增加分析時間。
[[email protected] ~]$ hawq config -s gp_analyze_relative_error
GUC		: gp_analyze_relative_error
Value		: 0.25

(4)統計更新

        不帶引數執行ANALYZE會更新當前資料庫中所有表的統計資訊,這可能需要執行很長時間。所以最好分析單個表,在一個表中的資料大量修改後分析該表。也可以選擇分析一個表列的子集,例如只分析連線、where子句、sort子句、group by子句、having子句中用到的列。
db1=# analyze t1 (name,category);
ANALYZE

(5)分析分割槽和AO表

        在分割槽表上執行ANALYZE命令時,它逐個分析每個葉級別的子分割槽。也可以只在新增或修改的分割槽檔案上執行ANALYZE,避免分析沒有變化的分割槽。
        analyzedb命令列應用自動跳過無變化的分割槽。並且它是多會話並行的,可以同時分析幾個分割槽。預設執行五個會話,會話數可以通過命令列的-p選項設定,值域為1-10。每次執行analyzedb,它都會將AO表和分割槽的狀態資訊儲存在master節點中資料目錄中的db_analyze目錄下,如/data/ha