基于函数值的递归 CTE 在 Postgres 12 上明显慢于 11

cis*_*cis 5 postgresql cte recursive postgresql-12 postgresql-performance

跟进我关于 Postgres 12 中的某些查询比 11 中的查询慢的问题,我认为我能够缩小问题的范围。似乎基于函数值的递归 CTE 是有问题的地方。

我能够分离出一个相当小的 SQL 查询,它在 Postgres 12.1 上运行的时间比在 Postgres 11.6 上运行的时间要长得多,例如 Postgres 12.1 中的大约 150 毫秒与 Postgres 11.6 中的大约 4 毫秒。我能够在各种系统上重现这种现象:在 VirtualBox 中的多个 VM 上;通过两台不同物理机器上的 Docker。(有关 docker 命令,请参阅附录)。然而,奇怪的是,我无法在https://www.db-fiddle.com/上重现它(在那里看不到区别,两者都很快)。

现在进行查询。首先,我们创建这个简单的函数

CREATE OR REPLACE FUNCTION public.my_test_function()
 RETURNS SETOF record
 LANGUAGE sql
 IMMUTABLE SECURITY DEFINER
AS $function$ 

SELECT 
        1::integer AS id,
        '2019-11-20'::date AS "startDate",
        '2020-01-01'::date AS "endDate"

$function$;
Run Code Online (Sandbox Code Playgroud)

然后对于实际查询

WITH  "somePeriods" AS  (
      SELECT * FROM my_test_function() AS 
      f(id integer, "startDate" date, "endDate" date)
),

"maxRecursiveEndDate" AS (

SELECT "startDate", "endDate", id, 
( 
  WITH RECURSIVE prep("startDateParam", "endDateParam") AS (

  SELECT "startDate","endDate" FROM "somePeriods" WHERE id = od.id
  UNION
  SELECT "startDate","endDate" FROM "somePeriods", prep
  WHERE
    "startDate" <= ("endDateParam" + '1 day'::interval ) AND ("endDateParam" + '1 day'::interval ) <= "endDate"
  )
  SELECT max("endDateParam") FROM prep
) AS "endDateNew"

FROM "somePeriods" AS od

)

SELECT * FROM "maxRecursiveEndDate";
Run Code Online (Sandbox Code Playgroud)

我猜这实际上在这里做什么并不重要。重要的一点可能是涉及多个 CTE,包括RECURSIVE一个。

我试过的:

  • 我没有尝试过my_test_function,即将值直接放入第一个 CTE。这样一来,就完全没有问题了。在 12 和 11 上运行同样快。
  • 在 Postgres 12 上,我使用了MATERIALIZED,但看不到任何效果。查询仍然像以前一样慢。

我不知道这是否真的可能是 Postgres 12 的错误(或性能回归),或者我是否在这里遗漏了一些东西。

附录:我用于复制的 Docker 命令

一、拉取两个版本的镜像

docker pull postgres:12.1
docker pull postgres:11.6
Run Code Online (Sandbox Code Playgroud)

现在,运行 Postgres 12

docker run -d --name my_postgres_12_container postgres:12.1
Run Code Online (Sandbox Code Playgroud)

现在,执行查询

docker exec my_postgres_12_container psql -U postgres -c "

CREATE OR REPLACE FUNCTION public.my_test_function()
 RETURNS SETOF record
 LANGUAGE sql
 IMMUTABLE SECURITY DEFINER
AS \$function\$ 

SELECT 
        1::integer AS id,
        '2019-11-20'::date AS \"startDate\",
        '2020-01-01'::date AS \"endDate\"

\$function\$;

EXPLAIN ANALYZE WITH  \"somePeriods\" AS  (
      SELECT * FROM my_test_function() AS 
      f(id integer, \"startDate\" date, \"endDate\" date)
),

\"maxRecursiveEndDate\" AS (

SELECT \"startDate\", \"endDate\", id, 
( 
  WITH RECURSIVE prep(\"startDateParam\", \"endDateParam\") AS (

  SELECT \"startDate\",\"endDate\" FROM \"somePeriods\" WHERE id = od.id
  UNION
  SELECT \"startDate\",\"endDate\" FROM \"somePeriods\", prep
  WHERE
    \"startDate\" <= (\"endDateParam\" + '1 day'::interval ) AND (\"endDateParam\" + '1 day'::interval ) <= \"endDate\"
  )
  SELECT max(\"endDateParam\") FROM prep
) AS \"endDateNew\"

FROM \"somePeriods\" AS od

)

SELECT * FROM \"maxRecursiveEndDate\";
"
Run Code Online (Sandbox Code Playgroud)

停止 Postgres 12 容器

docker stop my_postgres_12_container
Run Code Online (Sandbox Code Playgroud)

启动 Postgres 11 进行比较

docker run -d --name my_postgres_11_container postgres:11.6
Run Code Online (Sandbox Code Playgroud)

在 Postgres 11 中执行查询

docker exec my_postgres_11_container psql -U postgres -c "

CREATE OR REPLACE FUNCTION public.my_test_function()
 RETURNS SETOF record
 LANGUAGE sql
 IMMUTABLE SECURITY DEFINER
AS \$function\$ 

SELECT 
        1::integer AS id,
        '2019-11-20'::date AS \"startDate\",
        '2020-01-01'::date AS \"endDate\"

\$function\$;

EXPLAIN ANALYZE WITH  \"somePeriods\" AS  (
      SELECT * FROM my_test_function() AS 
      f(id integer, \"startDate\" date, \"endDate\" date)
),

\"maxRecursiveEndDate\" AS (

SELECT \"startDate\", \"endDate\", id, 
( 
  WITH RECURSIVE prep(\"startDateParam\", \"endDateParam\") AS (

  SELECT \"startDate\",\"endDate\" FROM \"somePeriods\" WHERE id = od.id
  UNION
  SELECT \"startDate\",\"endDate\" FROM \"somePeriods\", prep
  WHERE
    \"startDate\" <= (\"endDateParam\" + '1 day'::interval ) AND (\"endDateParam\" + '1 day'::interval ) <= \"endDate\"
  )
  SELECT max(\"endDateParam\") FROM prep
) AS \"endDateNew\"

FROM \"somePeriods\" AS od

)

SELECT * FROM \"maxRecursiveEndDate\";
"
Run Code Online (Sandbox Code Playgroud)

cis*_*cis 5

感谢 pgbugs 邮件列表中乐于助人的人,我发现在 PostgreSQL 12 中默认启用即时编译(一些有用的背景信息可以在这里找到)是我的问题。

运行我的查询SET jit = off;解决了这个问题:没有它,我的查询运行得很快。