PostgreSQL v12.6 中的 PLpgSQL INSERT-RETURNING-INTO 错误?

Posted

技术标签:

【中文标题】PostgreSQL v12.6 中的 PLpgSQL INSERT-RETURNING-INTO 错误?【英文标题】:PLpgSQL INSERT-RETURNING-INTO bug at PostgreSQL v12.6? 【发布时间】:2021-04-07 11:08:30 【问题描述】:

声明的 PLpgSQL 变量的命令 INSERT ... RETURNING c1 INTO v1 在第二次使用时失去价值。

在下面的完整函数中,“v1”变量是q_file_id:它在第一个 INSERT (jins) 处具有非零值,但在第二个 INSERT ( ins2),或无法识别数据依赖关系。

CREATE or replace FUNCTION ingest.geojson_load(
    p_file text, p_ftid int, p_ftype text DEFAULT NULL
) RETURNS text AS $f$
 DECLARE
    q_file_id integer;
    q_ret text;
 BEGIN
  INSERT INTO ingest.file(ftid,file_type,file_meta)
     SELECT p_ftid::smallint,
            COALESCE( p_ftype, substring(p_file from '[^\.]+$') ),
            geojson_readfile_headers(p_file)
  RETURNING file_id INTO q_file_id;
  RAISE NOTICE 'The File_id is %', q_file_id;

  WITH jins AS ( -- FIRST use is working fine!
    INSERT INTO ingest.tmp_geojson_feature
     SELECT * 
     FROM geojson_readfile_features_jgeom(p_file, q_file_id )
    RETURNING 1
   ), ins2 AS ( -- bug at this SECOND use of q_file_id
    INSERT INTO ingest.feature
     SELECT file_id, feature_id, properties, ST_GeomFromGeoJSON(jgeom)
     FROM ingest.tmp_geojson_feature
     WHERE file_id = q_file_id
    RETURNING 1
   )
   SELECT 'Hello jins='|| (SELECT COUNT(*) FROM jins) 
         ||' items from file_id '|| q_file_id ||'.'
         ||E'\n ins2= '|| (SELECT COUNT(*) FROM ins2) ||' items.'
         INTO q_ret;
  -- if no bug: DELETE FROM ingest.tmp_geojson_feature WHERE file_id = q_file_id;
  RETURN q_ret;
 END;
$f$ LANGUAGE PLpgSQL;

Bug 证据:函数显示“Hello...ins2=0”,但是当 RAISE NOTICE 为 8 并且我只运行 8 的 INSERT 片段 ins2 时,它工作正常。

    INSERT INTO ingest.feature
     SELECT file_id, feature_id, properties, ST_GeomFromGeoJSON(jgeom)
     FROM ingest.tmp_geojson_feature
     WHERE file_id = 8
   -- inserted!

注意事项

带着这个证据回过头来看函数,第一个INSERT被分开以避免执行并发,所以一个自然的解决方案也是把WITH语句拆分成两个INSERTS的序列……但是问题这里不是“如何避免问题”,是关于为什么(?)PostgreSQL planner/optimizer 没有优化 WITH 子句的“INSERT 数据流”。

我们可以想象 planner 在后台进行拆分,或者识别 数据管道并检查一种管道并发的机会-将两个INSERT分成多个阶段以使用stream processing strategy。


(在@LaurenzAlbe 的回答后编辑)

PS:“INSERT 的数据流”可以优化。 Big data streams 和 small execution pipelines 是流行的优化策略,使用相同的概念。它存在于数据流应用程序和许多语言中,如Go、Scala、...或Spark等框架。

也许专家说“没有需要识别的数据依赖性”是很自然的......所以,如果它不是 PostgreSQL 的错误(也许我的wrong hypothesis 导致了一个错误的问题),一个子问题是“如何对 PostgreSQL 规划者说有一个优化(管道)机会,是可能的吗?”;或“为什么规划者不使用这种优化机会?”

【问题讨论】:

【参考方案1】:

正如所料,这个错误是你的。见the documentation:

WITH 中的子语句彼此并发执行,并与主查询同时执行。因此,当在WITH 中使用数据修改语句时,指定更新实际发生的顺序是不可预测的。所有语句都使用相同的快照执行(请参阅Chapter 13),因此它们无法“看到”彼此对目标表的影响。这减轻了行更新的实际顺序的不可预测性的影响,并且意味着RETURNING 数据是在不同的WITH 子语句和主查询之间传达更改的唯一方式。

您的误解似乎是 SQL 是一种过程语言,其中语句的某些部分以特定顺序执行。但 SQL 不是,你不能依赖它。

解决办法很简单:使用两条单​​独的SQL语句,然后第二条就可以看到第一条的效果了。

【讨论】:

嗨,有趣的好线索。但没有意义:PLpgSQL是一种顺序语言,步骤顺序为: 1. "INSERT INTO ...RETURNING file_id INTO q_file_id"; 2. RAISE NOTICE q_file_id"; 3. "WITH ... using q_file_id"。所以q_file_id 必须对于所有 WITH 语句都相同。 ...哼...第二个INSERT依赖第一个INSERT,ok,不过我需要一个pipeline INSERT execution,优化一下就好,每个INSERT作为一个CPU进程... PostgreSQL 优化器必须检测存在“数据流依赖”(不能是完全并行的过程,它只是流水线级别的并发)。所以问题是“有意义吗?”。如果答案是肯定的,我们就断定这是一个错误;否则(如果我的解释是错误的)那是我的失败。 你在说大话,但这与 CPU 实现无关。当然,PostgreSQL 可以以不同的方式工作,但它不需要。我没有发现问题 - 运行两个不同的 SQL 语句。 您好,很抱歉使用“CPU”这个词,这与“CPU 规模”无关,而且我不是数据库并发方面的专家...在 PostgreSQL宇宙,正确的行话它可能是“快照”(或“线程”或“并行事务”......)。我评论的重点是管道并行性(一个与规模无关的概念),然后一个子问题是“为什么 PostgreSQL 不使用管道?”。 您基本上是在抱怨,因为 PostgreSQL 的工作方式与您希望的不同。 “为什么”问题大多是无用的:答案总是“因为它的设计/实现方式不同”。要么以 PostgreSQL 的方式做事,要么使用不同的数据库。

以上是关于PostgreSQL v12.6 中的 PLpgSQL INSERT-RETURNING-INTO 错误?的主要内容,如果未能解决你的问题,请参考以下文章

如何从PostgreSQL的嵌套xml对象中提取值?

Orange.Technologies.Cadpipe.Suite.v12.6 1CD(管道设计软件)

Orange.Technologies.Cadpipe.Suite.v12.6 1CD(管道设计软件)

自动更改输入文本的语言 PB 12.6

Postgresql 9.2 错误与 group by 未出现在 postgresql 12 中

12.6 水王