分布表DML操作说明

更新时间:
复制为 MD 格式

PolarDB PostgreSQL分布式版集群中,您可以使用标准的PostgreSQL DML命令(如INSERTUPDATEDELETE)来操作数据。虽然语法保持一致,但其执行行为和性能表现与数据在节点间的分布方式密切相关。为确保数据操作的正确性和高效性,请首先了解以下核心原则。

DML操作的核心原则与限制

  • 插入时必须指定分布列:执行INSERT语句时,必须包含分布列。系统需要根据分布列计算哈希值,以确定数据应插入到哪个物理分片。若缺少分布列,操作将失败。

    示例:假设id是分布列,若插入时未包含分布列id,则会报错。

    INSERT INTO t (data) VALUES ('TEST');

    返回结果如下:

    ERROR:  cannot perform an INSERT without a partition column value
  • 不支持修改分布列的值:一旦数据行被写入,其所在的位置就由分布列的值确定。因此,在UPDATEINSERT ... 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:将数据从源表导入目标表。

    1. 创建源表与目标表。

      -- 源表
      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');
    2. 导入数据。

      -- 当源表与目标表结构一致时
      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':从数据库(CNDN节点)的文件系统读取文件。

    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执行模式

执行模式

性能评级

触发条件

核心思想

下推并行执行

★★★★★

  • 仅涉及一张分布表。

  • 涉及多张分布表时,需满足以下所有条件:

    • 由多张分布表或复制表组成。

    • 分布表同属于一个亲和组

    • UPDATE/DELETE命令涉及的分布表均使用分布列进行关联。

计算完全下推到各数据节点并行执行,无节点间数据传输。

重新分区执行

★★

INSERT...SELECT中,源表和目标表的分布键不匹配。

将源表数据在节点间重新分发,以匹配目标表的分布规则。

上拉并重定向执行

DML语句中包含ORDER BYLIMITOFFSETGROUP BY(如果分组键不包含分布列)等子句。

将中间结果拉取到协调节点处理,再下发到目标节点,网络开销大。

下推并行执行

  • DML操作仅涉及一张分布表。

    -- 简单插入语句
    INSERT INTO t VALUES (1, 'TEST'), (2, 'TEST');
    -- 简单更新语句
    UPDATE t SET data = 'CHANGE' WHERE id = 1;
    -- 简单删除语句
    DELETE FROM t WHERE id = 1;
  • DML操作多张分布表。

    示例

    1. 假设存在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');
    2. 查看查询亲和组信息。

      SELECT logicalrelid, colocationid FROM pg_dist_partition;

      返回结果如下:

       logicalrelid | colocationid
      --------------+--------------
       target       |            1
       source       |            1
    3. 当执行如下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;
    4. 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操作时,则可能触发数据重分区。当触发重分区后,源节点会将需要的数据打包并转发到匹配的目的节点上,用于后续命令的执行。

示例

  1. 假设存在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');
  2. 当执行如下INSERT...SELECT命令时,将会触发重新分区执行,执行计划中会显示当前命令的执行方式repartitionINSERT的目标列是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 BYLIMITOFFSETGROUP BY(如果分组键不包含分布列))时,数据必须先被拉取到协调节点(Coordinator Node,CN),在CN上完成合并操作后,再将结果重新下发到目标节点。这会造成CN节点的瓶颈和大量的网络传输。

示例

  1. 假设存在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');
    
  2. 当执行如下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)