暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

PostgreSQL 18:第 1 部分或 CommitFest 2024-07

通讯员 2025-01-15
381

本文是关于即将发布的 PostgreSQL 18 版本的系列文章中的第一篇。让我们看一下 7 月 CommitFest 中引入的功能。


Planner:Hash Right Semi Join 支持
Planner:实现用于并行嵌套循环连接的内部行集
Planner 支持generate_series
EXPLAIN (analyze):Parallel Bitmap Heap Scan 节点工作程序的统计信息
复合类型的函数 min 和 max
regexp* 函数的参数名称
  pgbench 中的调试模式
pg_get_backend_memory_contexts:列路径而不是父级,新的列类型
功能pg_get_acl
pg_upgrade:pg_dump优化
预定义角色pg_signal_autovacuum_worker



Planner:Hash Right Semi Join 支持
提交:aa86129e1


以下 PostgreSQL 17 中的计划对两个表采用哈希联接和顺序扫描。


17=# EXPLAIN (costs off, analyze)
SELECT * FROM flights
WHERE flight_id IN (SELECT flight_id FROM ticket_flights);

                                           QUERY PLAN                                           
------------------------------------------------------------------------------------------------
 Hash Join (actual time=2133.122..2195.619 rows=150588 loops=1)
   Hash Cond: (flights.flight_id = ticket_flights.flight_id)
   ->  Seq Scan on flights (actual time=0.018..10.301 rows=214867 loops=1)
   ->  Hash (actual time=2132.969..2132.970 rows=150588 loops=1)
         Buckets: 262144 (originally 131072)  Batches: 1 (originally 1)  Memory Usage: 7343kB
         ->  HashAggregate (actual time=1821.476..2114.218 rows=150588 loops=1)
               Group Key: ticket_flights.flight_id
               Batches: 5  Memory Usage: 10289kB  Disk Usage: 69384kB
               ->  Seq Scan on ticket_flights (actual time=7.200..655.356 rows=8391852 loops=1)
 Planning Time: 0.325 ms
 Execution Time: 2258.237 ms
(11 rows)

但是,哈希值构建在较大的 table ticket_flights 上,与较小的 table flight 相比,这显然需要更多的资源。


在 PostgreSQL 18 中,可以选择对哪个表进行哈希处理。在这里,在第 4 行中,我们可以看到 planner 与 Hash Right Semi Join 一起使用,甚至处于并行模式。因此,执行时间显著减少:


                                            QUERY PLAN                                             
---------------------------------------------------------------------------------------------------
 Gather (actual time=56.771..943.233 rows=150590 loops=1)
   Workers Planned: 2
   Workers Launched: 2
   ->  Parallel Hash Right Semi Join (actual time=41.754..909.894 rows=50197 loops=3)
         Hash Cond: (ticket_flights.flight_id = flights.flight_id)
         ->  Parallel Seq Scan on ticket_flights (actual time=0.047..221.511 rows=2797284 loops=3)
         ->  Parallel Hash (actual time=40.309..40.309 rows=71622 loops=3)
               Buckets: 262144  Batches: 1  Memory Usage: 23808kB
               ->  Parallel Seq Scan on flights (actual time=0.008..6.631 rows=71622 loops=3)
 Planning Time: 0.555 ms
 Execution Time: 949.831 ms
(11 rows)


Planner:实现用于并行嵌套循环连接的内部行集
提交: 22d946b0f


在 PostgreSQL 18 之前,规划器从未考虑过为并行嵌套循环连接实现内部行集。


17=# EXPLAIN (costs off)
SELECT *
FROM ticket_flights tf
JOIN flights f USING (flight_id)
WHERE f.flight_id = 12345;

                     QUERY PLAN                     
----------------------------------------------------
 Nested Loop
   ->  Index Scan using flights_pkey on flights f
         Index Cond: (flight_id = 12345)
   ->  Gather
         Workers Planned: 2
         ->  Parallel Seq Scan on ticket_flights tf
               Filter: (flight_id = 12345)
(7 rows)

PostgreSQL 18 中相同查询的计划具体化了 flights:


                          QUERY PLAN                          
--------------------------------------------------------------
 Gather
   Workers Planned: 2
   ->  Nested Loop
         ->  Parallel Seq Scan on ticket_flights tf
               Filter: (flight_id = 12345)
         ->  Materialize
               ->  Index Scan using flights_pkey on flights f
                     Index Cond: (flight_id = 12345)
(8 rows)

值得注意的是,新计划的性能没有太大变化。在这两个计划中,都执行相同的操作,但规划者在其他情况下有更多机会选择最佳计划,这是个好消息。



Planner 支持generate_series
提交:036BDCC9


新的支持函数告诉 planner generate_series 在一系列日期和时间下将返回多少行:


EXPLAIN
SELECT *
FROM generate_series(current_date, current_date + '1 day'::interval, '1 hour');

                             QUERY PLAN                              
---------------------------------------------------------------------
 Function Scan on generate_series  (cost=0.01..0.26 rows=25 width=8)
(1 row)

以前,行数始终估计为 1000(函数的默认 ROWS 值)。


请注意,类似的整数支持函数长期存在:


EXPLAIN
SELECT *
FROM generate_series(1, 42, 2);

                             QUERY PLAN                              
---------------------------------------------------------------------
 Function Scan on generate_series  (cost=0.00..0.21 rows=21 width=4)
(1 row)


EXPLAIN (analyze):Parallel Bitmap Heap Scan 节点工作程序的统计信息
提交:5a1e6df3b


EXPLAIN 命令现在显示并行位图扫描中涉及的每个工作程序的统计信息(精确和有损片段的数量)。在下面的示例中,这些是以 Worker 0 和 Worker 1 开头的行。


EXPLAIN (analyze, costs off, timing off, summary off)
SELECT count(*) FROM bookings
WHERE total_amount < 20000 AND book_date > '2017-07-15';

                                             QUERY PLAN                                              
-----------------------------------------------------------------------------------------------------
 Finalize Aggregate (actual rows=1 loops=1)
   ->  Gather (actual rows=3 loops=1)
         Workers Planned: 2
         Workers Launched: 2
         ->  Partial Aggregate (actual rows=1 loops=3)
               ->  Parallel Bitmap Heap Scan on bookings (actual rows=6415 loops=3)
                     Recheck Cond: (total_amount < '20000'::numeric)
                     Filter: (book_date > '2017-07-15 00:00:00+03'::timestamp with time zone)
                     Rows Removed by Filter: 67072
                     Heap Blocks: exact=6345
                     Worker 0:  Heap Blocks: exact=3632
                     Worker 1:  Heap Blocks: exact=3470
                     ->  Bitmap Index Scan on bookings_total_amount_idx (actual rows=220460 loops=1)
                           Index Cond: (total_amount < '20000'::numeric)


复合类型的函数 min 和 max
提交:a0f1fce80


复合类型值可以相互比较,并且已经有一段时间了。比较是逐个元素完成的。例如,让我们对以下行进行排序:


CREATE TABLE seats(
line text,
number integer
);

INSERT INTO seats VALUES
('A', 42), ('B', 1), ('C', 27);

SELECT * FROM seats s ORDER BY s DESC;

 line | number
------+--------
 C    |     27
 B    |      1
 A    |     42
(3 rows)

但是,没有用于复合类型的聚合函数 min 和 max。直到现在。


SELECT min(s.*), max(s.*) FROM seats s;

  min   |  max   
--------+--------
 (A,42) | (C,27)
(1 row)


regexp* 函数的参数名称
提交: 580f8727


正则表达式的函数具有参数名称以提高清晰度:


SELECT regexp_replace(
pattern => '$',
replacement => 'Postgres!',
string => 'All You Need Is '
);

      regexp_replace       
---------------------------
 All You Need Is Postgres!

参数名称在文档或 psql 的 \df 命令输出中列出。以前,参数只能按位置传递。



  pgbench 中的调试模式
提交:3ff01b2b


大多数 Server Utilities 使用 -d 选项来指定要连接的数据库。但是,在 pgbench 中,此选项进入了 debug 模式。为了保持一致性,更改了参数名称:现在,您使用 -d 或 --dbname 指定数据库,并使用全名选项 --debug 输入更多调试。


此更改已在 PostgreSQL 17 中实施,尽管它仅在 7 月份的 PostgreSQL 18 CommitFest 中正式引入。



pg_get_backend_memory_contexts:列路径而不是父级,新的列类型
提交:32d3ed81f0d1127512227a1d


研究内存使用情况的核心开发人员和爱好者会喜欢 pg_backend_memory_contexts 视图中的变化。


此修补程序使链接子上下文和父上下文更加方便和可靠。有一个新的 column path,其中包含上下文 ID 数组。数组的第一个元素始终是顶级上下文 TopMemoryContext,最后一个元素是当前行上下文 ID。此外,level 列中的编号现在从 1 而不是 0 开始,这更适合编写查询。


下面是一个查询示例,其中显示了 TopPortalContext 及其所有子上下文的内存上下文。


WITH memory_contexts AS (
SELECT * FROM pg_backend_memory_contexts
)
SELECT child.name, child.type, child.level, child.path, child.total_bytes
FROM memory_contexts parent, memory_contexts child
WHERE parent.name = 'TopPortalContext' AND
child.path[parent.level] = parent.path[parent.level];

              name              |    type    | level |         path          | total_bytes
--------------------------------+------------+-------+-----------------------+-------------
 TopPortalContext               | AllocSet   |     2 | {1,20}                |        8192
 PortalContext                  | AllocSet   |     3 | {1,20,31}             |        1024
 ExecutorState                  | AllocSet   |     4 | {1,20,31,136}         |       65632
 tuplestore tuples              | Generation |     5 | {1,20,31,136,138}     |       32768
 printtup                       | AllocSet   |     5 | {1,20,31,136,139}     |        8192
 ExprContext                    | AllocSet   |     5 | {1,20,31,136,140}     |        8192
 ExprContext                    | AllocSet   |     5 | {1,20,31,136,141}     |        8192
 tuplestore tuples              | Generation |     5 | {1,20,31,136,142}     |        8192
 ExprContext                    | AllocSet   |     5 | {1,20,31,136,143}     |        8192
 Table function arguments       | AllocSet   |     5 | {1,20,31,136,144}     |        8192
 ExprContext                    | AllocSet   |     5 | {1,20,31,136,145}     |       32768
 pg_get_backend_memory_contexts | AllocSet   |     6 | {1,20,31,136,145,146} |       16384
(12 rows)

以前,可以通过将列(被第二次提交删除)链接到 name 列来连接上下文。这种方法需要更复杂的递归查询。更重要的是,这种链接不太可靠,因为不能保证上下文名称是唯一的。


最后,第三个提交添加 type 列。目前,使用了四种类型的上下文:AllocSetGenerationSlabBump。有关内存上下文的更多信息,请参阅源代码:src/backend/utils/mmgr/README



功能pg_get_acl
提交:4564F1CEE311C6E5D898665B


以下命令使表 test 依赖于角色 alice


CREATE ROLE alice;
CREATE TABLE test (id int);
GRANT SELECT ON test TO alice;

现在,如果不分离依赖表,则无法删除角色 alice


DROP ROLE alice;

ERROR:  role "alice" cannot be dropped because some objects depend on it
DETAIL:  privileges for table test

对象依赖关系数据存储在两个系统目录中:pg_depend(同一数据库中的对象之间)和共享集群对象(例如角色)的pg_shdepend


目前,pg_shdepend仅包含我们刚刚创建的依赖项的记录。


SELECT * FROM pg_shdepend\gx

-[ RECORD 1 ]-----
dbid       | 5
classid    | 1259
objid      | 16528
objsubid   | 0
refclassid | 1260
refobjid   | 16527
deptype    | a

此信息不易读取,因为对象表示为标识符:系统目录表 ID (classid)、系统目录中的行 ID (objid) 和表的序号列号 (objsubid)。引用的对象分别由 refclassid 和 refobjid 表示(没有对表列的引用,因此没有 refobjsubid 列)。


有许多方便的函数可用于通过对象的 ID 访问对象。例如,函数 pg_identify_object 返回对象记录:


SELECT * FROM pg_identify_object(1259,16528,0);

 type  | schema | name |  identity   
-------+--------+------+-------------
 table | public | test | public.test

表所依赖的对象:


SELECT * FROM pg_identify_object(1260,16527,0);

 type | schema | name  | identity
------+--------+-------+----------
 role |        | alice | alice

这组函数新增了一个功能。新函数 pg_get_acl 返回对象访问权限,而无需查询特定的系统目录。


此查询显示角色 alice 具有哪些权限:


SELECT *,
pg_identify_object(classid, objid, objsubid) AS object,
pg_identify_object(refclassid, refobjid, 0) AS ref_object,
pg_get_acl(classid, objid, objsubid)
FROM pg_shdepend\gx

-[ RECORD 1 ]---------------------------------------------
dbid       | 5
classid    | 1259
objid      | 16528
objsubid   | 0
refclassid | 1260
refobjid   | 16527
deptype    | a
object     | (table,public,test,public.test)
ref_object | (role,,alice,alice)
pg_get_acl | {postgres=arwdDxtm/postgres,alice=r/postgres}


pg_upgrade:pg_dump优化
提交:6e16b1e464f34eb2e2329cad1b23687e92568e962998c8b06bb96bd15b7db46ebeeae29


服务器版本升级过程有几项新的优化。让我们一起考虑它们,因为它们都或多或少与 pg_dump 相关, 是在升级期间转储系统目录内容的工具。


第一个优化是 pg_upgrade 将使用 --no-sync 键pg_dump运行。在升级服务器时,保证文件刷新到磁盘是多余的:如果出现问题,整个升级过程无论如何都需要从头开始,而磁盘同步时间是需要节省时间的。


第二次优化使数据库创建命令使用 FILE_COPY 策略而不是默认WAL_LOG:


CREATE DATABASE .. STRATEGY = FILE_COPY;

使用此策略,不会记录模板数据库的内容,并且通过在复制文件之前和之后执行检查点来保证持久性。在正常操作中,这不是最有效的方法,但在二进制升级期间,服务器无论如何都不会执行额外的检查点(在创建模板数据库 template0 之后,只有一个检查点)。


这两项优化的有效性将随着升级集群中的数据库数量而增加。


第三次优化加快了 --binary-upgrade 模式下pg_dump表描述的 dump 速度。在此模式下,pg_upgrade pg_dump启动。当它启动时,pg_dump 通过一个查询来获取有关所有表的信息pg_class并将其存储在内存中的有序数组中,从而节省时间。在此补丁之前,每个表都有一个单独的查询要pg_class,这大大增加了成本。


第四次优化加快了序列的转储速度,就像上一个补丁对 table 所做的那样。当 pg_dump 启动时,它会将所有序列数据放入一个有序数组中,这样就无需将来每次都访问系统目录。值得注意的是,此优化不限于 --binary-upgrade 模式,并且在常规模式下工作正常。


第三次和第四次优化在存储表和序列信息所需的 RAM 形式上进行了小的权衡,但补丁背后的开发人员认为这绝对值得。


在具有少量表和序列的数据库中,最后两次优化不会产生太大影响。另一方面,在具有数万个表和序列的数据库中,速度的提高实际上是明显的。


让我们测试一下。在 PostgreSQL 17 中创建一个包含 100 000 个表的数据库,每个表都有一个链接到序列的列。然后在 PostgreSQL 18 中执行相同的操作。


$ psql -d test -o /dev/null <<EOF
SELECT format('CREATE TABLE t%s (id int GENERATED ALWAYS AS IDENTITY)', g.i)
FROM generate_series(1,100_000) AS g(i)
\gexec
EOF

现在,计算 pg_dump --binary-upgrade 需要多长时间。


  PostgreSQL 17 版本:


$ time pg_dump -d test --schema-only --binary-upgrade > /dev/null

real    1m40,677s
user    0m5,403s
sys    0m3,265s

  PostgreSQL 18 版本:


$ time pg_dump -d test --schema-only --binary-upgrade > /dev/null

real    0m7,427s
user    0m2,220s
sys    0m0,264s

这些补丁使其运行速度提高了十倍以上。



预定义角色pg_signal_autovacuum_worker
提交:ccd38024b


预定义角色 pg_signal_backend 的成员可以将查询终止信号 (pg_cancel_backend) 和进程终止信号 (pg_terminate_backend) 发送到服务器后端进程。但有一个例外:目标进程可能不属于超级用户。


但是,在需要中断 autovacuum 进程时,可能会有维护方案。新的预定义角色pg_signal_autovacuum_worker的成员虽然不是超级用户,但可以发送 autovacuum worker 信号来终止当前的表清理作业或使用相同的两个函数完全终止进程。





这就是现在的全部内容。9 月 CommitFest 的消息很快就会出来!


原文标题:PostgreSQL 18: Part 1 or CommitFest 2024-07

原文作者: Pavel Luzanov

原文链接:https://habr.com/en/companies/postgrespro/articles/860490/

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论