在PolarDB PostgreSQL分布式版叢集中,您可以使用標準的PostgreSQL DML命令(如INSERT, UPDATE, DELETE)來操作資料。雖然文法保持一致,但其執行行為和效能表現與資料在節點間的分布方式密切相關。為確保資料操作的正確性和高效性,請首先瞭解以下核心原則。
DML操作的核心原則與限制
插入時必須指定分布列:執行
INSERT語句時,必須包含分布列。系統需要根據分布列計算雜湊值,以確定資料應插入到哪個物理分區。若缺少分布列,操作將失敗。樣本:假設
id是分布列,若插入時未包含分布列id,則會報錯。INSERT INTO t (data) VALUES ('TEST');返回結果如下:
ERROR: cannot perform an INSERT without a partition column value不支援修改分布列的值:一旦資料行被寫入,其所在的位置就由分布列的值確定。因此,在
UPDATE或INSERT ... ON CONFLICT DO UPDATE語句中,不允許修改分布列的值,因為這會涉及到資料在物理節點間的遷移,此類操作不被支援。樣本:假設
id是分布列,先插入id=1的值後,嘗試修改分布列的值。-- 插入未經處理資料 INSERT INTO t (id, data) VALUES (1, 'TEST'); -- 嘗試修改分布列的值 UPDATE t SET id = 10 WHERE id = 1;返回結果如下:
ERROR: modifying the partition value of rows is not allowed跨節點事務的一致性說明:PolarDB PostgreSQL分布式版使用兩階段交易認可(2PC)來保證分散式交易的原子性。但在預設的讀已提交(Read Committed)隔離等級下,暫不支援跨節點的快照隔離(Snapshot Isolation,SI)。
這意味著,一個涉及多個資料節點(DN)的事務,其在各節點上的提交時間點可能存在微小延遲。在此短暫的間隙中,並發的
SELECT查詢可能會觀察到不一致的中間狀態(例如,在一個節點上看到新資料,在另一個節點上仍是舊資料)。如果您的業務對跨節點資料一致性有嚴格要求,需要在應用程式層進行特殊處理,如使用鎖或規避讀取最新資料。
常用DML操作
INSERT:插入資料
支援單行、多行插入以及ON CONFLICT子句來處理資料衝突。
單行插入。
-- 隱式列文法,按照表中列的順序插入資料。 INSERT INTO t VALUES (1, 'TEST'); -- 顯示列文法,明確指定要為哪些列插入資料。 INSERT INTO t (id, data) VALUES (1, 'TEST');批量插入。
INSERT INTO t VALUES (1, 'TEST'), (2, 'TEST'), (3, 'TEST');INSERT...SELECT:將資料從源表匯入目標表。建立源表與目標表。
-- 源表 CREATE TABLE source (id int, data text); SELECT create_distributed_table('source', 'id'); -- 目標表 CREATE TABLE target (id int, data text); SELECT create_distributed_table('target', 'id');匯入資料。
-- 當源表與目標表結構一致時 INSERT INTO target SELECT * FROM source; -- 手動指定目標列集與資料列集 INSERT INTO target(id, data) SELECT id, data FROM source;
ON CONFLICT:處理資料衝突。問題:如果插入表存在主鍵約束(PRIMARY KEY)、唯一性限制式(UNIQUE)或者排他性約束(EXCLUDE)時,插入資料時可能會出現衝突。可能出現報錯如下:
ERROR: duplicate key value violates unique constraint "xxx"解決方式:
ON CONFLICT DO NOTHING:存在衝突時,忽略新資料。INSERT INTO target VALUES (1, 'NEW_DATA') ON CONFLICT(id) DO NOTHING;ON CONFLICT DO UPDATE:存在衝突時,更新指定列。同時,PolarDB PostgreSQL分布式版支援使用EXCLUDED來引用被衝突排除的新值。-- 當存在衝突時,更新指定列為固定值,例如'ERROR'。 INSERT INTO target VALUES (1, 'NEW_DATA') ON CONFLICT(id) DO UPDATE SET data='ERROR';-- 使用EXCLUDED來引用被衝突排除的新值 INSERT INTO target VALUES (1, 'NEW_DATA') ON CONFLICT(id) DO UPDATE SET data = EXCLUDED.data;
UPDATE/DELETE:更新與刪除資料
文法與標準PostgreSQL完全相同。建議使用WHERE子句高效地定位到目標資料。
更新資料:
UPDATE t SET data = 'CHANGED' WHERE id = 1;刪除資料:
DELETE FROM t WHERE id = 1;
COPY:批量載入資料
使用COPY命令可以從檔案高效地載入大量資料。請注意psql用戶端中的COPY與\COPY的區別:
COPY ... FROM 'file_path':從資料庫(CN或DN節點)的檔案系統讀取檔案。COPY target FROM '/path/to/data.csv' WITH CSV HEADER;\COPY ... FROM 'file_path':從執行psql命令的用戶端機器讀取檔案,並將其串流到資料庫。\COPY target FROM '/path/on/client/data.csv' WITH CSV HEADER;
DML效能最佳化
複雜的DML(尤其是INSERT...SELECT和多表UPDATE/DELETE)有多種執行模式,效能差異巨大。您可以通過EXPLAIN命令查看執行計畫,判斷其執行模式。
DML執行模式
執行模式 | 效能評級 | 觸發條件 | 核心思想 |
下推並存執行 | ★★★★★ |
| 計算完全下推到各資料節點並存執行,無節點間資料轉送。 |
重新分區執行 | ★★ |
| 將源表資料在節點間重新分發,以匹配目標表的分布規則。 |
上拉並重新導向執行 | ★ | DML語句中包含 | 將中間結果拉取到協調節點處理,再下發到目標節點,網路開銷大。 |
下推並存執行
DML操作僅涉及一張分布表。
-- 簡單插入語句 INSERT INTO t VALUES (1, 'TEST'), (2, 'TEST'); -- 簡單更新語句 UPDATE t SET data = 'CHANGE' WHERE id = 1; -- 簡單刪除語句 DELETE FROM t WHERE id = 1;DML操作多張分布表。
樣本:
假設存在
source表與target表,表結構如下所示,兩表處於同一個親和組,分布列均為id列:-- 源表 CREATE TABLE source (id int, data text); SELECT create_distributed_table('source', 'id'); -- 目標表 CREATE TABLE target (id int, data text); SELECT create_distributed_table('target', 'id');查看查詢親和組資訊。
SELECT logicalrelid, colocationid FROM pg_dist_partition;返回結果如下:
logicalrelid | colocationid --------------+-------------- target | 1 source | 1當執行如下DML操作時,可以在單一節點內部執行,因此可以下推並存執行:
-- 插入語句樣本 INSERT INTO target(id, data) SELECT id, data FROM source; -- 更新語句樣本 UPDATE target SET data = source.data FROM source WHERE target.id = source.id; -- 刪除語句樣本 DELETE FROM target USING source WHERE target.id = source.id;以
INSERT...SELECT命令為例,典型可下推的命令將會產生若干獨立、可下推執行的子任務:EXPLAIN INSERT INTO target(id, data) SELECT id, data FROM source;返回結果如下:
QUERY PLAN -------------------------------------------------------------------------------------------------- Custom Scan (PolarCluster Adaptive) (cost=0.00..0.00 rows=0 width=0) Task Count: 4 --任務被分發到4個分區上執行 Tasks Shown: One of 4 -- 每個分區都在本地執行INSERT...SELECT -> Task Node: host=10.xxx.xxx.xxx port=3006 dbname=testdb -> Insert on target_102105 polar_cluster_table_alias (cost=0.00..22.70 rows=0 width=0) -> Seq Scan on source_102101 source (cost=0.00..22.70 rows=1264 width=36) Filter: (id IS NOT NULL) (8 rows)
重新分區執行
當INSERT...SELECT的源表和目標表分布規則不一致時,即不能在單一節點內執行DML操作時,則可能觸發資料重分區。當觸發重分區後,源節點會將需要的資料打包並轉寄到匹配的目的節點上,用於後續命令的執行。
樣本:
假設存在
source2表與target2表,表結構如下所示:-- 源表 CREATE TABLE source2 (id int, t_id int, data text); SELECT create_distributed_table('source2', 'id'); -- 目標表 CREATE TABLE target2 (id int, data text); SELECT create_distributed_table('target2', 'id');當執行如下
INSERT...SELECT命令時,將會觸發重新分區執行,執行計畫中會顯示當前命令的執行方式repartition。INSERT的目標列是id,但SELECT出來作為id的卻是t_id列。EXPLAIN INSERT INTO target2(id, data) SELECT t_id, data FROM source2;返回結果如下:
QUERY PLAN --------------------------------------------------------------------------------------------- Custom Scan (PolarCluster INSERT ... SELECT) (cost=0.00..0.00 rows=0 width=0) INSERT/SELECT method: repartition -- 明確提示發生了資料重分區 -> Custom Scan (PolarCluster Adaptive) (cost=0.00..0.00 rows=100000 width=36) Task Count: 4 Tasks Shown: One of 4 -> Task Node: host=10.188.91.26 port=3006 dbname=testdb -> Seq Scan on source2_102117 source2 (cost=0.00..22.00 rows=1200 width=36) (8 rows)
上拉並重新導向執行
當DML語句包含無法下推的子句(如ORDER BY、LIMIT、OFFSET、GROUP BY(如果分組鍵不包含分布列))時,資料必須先被拉取到協調節點(Coordinator Node,CN),在CN上完成合併操作後,再將結果重新下發到目標節點。這會造成CN節點的瓶頸和大量的網路傳輸。
樣本:
假設存在
source3表與target3表,表結構如下所示:-- 源表 CREATE TABLE source3 (id int, t_id int, data text); SELECT create_distributed_table('source3', 'id'); -- 目標表 CREATE TABLE target3 (id int, data text); SELECT create_distributed_table('target3', 'id');當執行如下
INSERT...SELECT命令時,將會觸發上拉並重新導向執行,執行計畫中會顯示當前命令的執行方式pull to coordinator。EXPLAIN INSERT INTO target3(id, data) SELECT t_id, data FROM source3 LIMIT 1;返回結果如下:
QUERY PLAN --------------------------------------------------------------------------------------------------------- Custom Scan (PolarCluster INSERT ... SELECT) (cost=0.00..0.00 rows=0 width=0) INSERT/SELECT method: pull to coordinator -- 明確提示資料被拉取到協調節點 -> Limit (cost=0.00..0.00 rows=1 width=36) -> Custom Scan (PolarCluster Adaptive) (cost=0.00..0.00 rows=100000 width=36) Task Count: 4 Tasks Shown: One of 4 -> Task Node: host=10.188.91.26 port=3006 dbname=testdb -> Limit (cost=0.00..0.02 rows=1 width=36) -> Seq Scan on source3_102125 source3 (cost=0.00..22.00 rows=1200 width=36) (10 rows)