在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)