PG 14新特性汇总
Tags: PG新特性汇总PostgreSQLPG 14
- PostgreSQL 14 ALTER TABLE DETACH 支持 CONCURRENTLY:分区表下线不再阻塞其它会话
- LZ4压缩可以用于TOAST数据
- 新增视图pg_stat_wal,跟踪wal日志的生成与写入磁盘情况
- pg_prepared_statements新增统计软/硬解析次数
- PostgreSQL 14新特性--减少索引膨胀
- 为什么会出现索引膨胀
- 当前用于改善索引膨胀和性能的特性
- HOT元组
- 杀死索引条目
- PG14如何进一步减少索引膨胀
- 示例
- 总结
- PostgreSQL 14中提升Nested Loop Joins性能的enable_memoize
- 什么是memoization?
- 开启/关闭该参数
- 使用benchmark
- 优化LATERAL
- 结论
- 原文
- 数据类型
- 函数
- 管理功能
- 复制和恢复
- 逻辑复制的改进
- 索引优化
- SQL特性
- 数据损坏
- VACUUM增强
- 扩展统计信息
- 性能改进
- PG14 监控和日志功能提升
- 查询标识符
- 自动vacuum和自动analyze 日志改进
- 连接日志
- 窗口函数incremental sort
- 并行查询多项改进
- 开发功能
- 函数或存储过程支持SQL语言
- group by distinct子句分组去重
- 新数据类型multirange
- JSON数据下标查询
- 存储过程事务控制
- 扩展date_bin函数
- 扩展SQL子句
- 安全提升
- 1.预定义角色权限管理
- 2.修改密码管理默认设置
- PostgreSQL V14中更好的SQL函数
- 为什么选择 SQL 函数?
- PostgreSQL函数的缺点
- PostgreSQL v14 中的新 SQL 函数语法
- 结论
- PG14 IO向量化
- 参考
PostgreSQL 14 ALTER TABLE DETACH 支持 CONCURRENTLY:分区表下线不再阻塞其它会话
从PG1开始,ALTER TABLE DETACH 支持 CONCURRENTLY,避免因ALTER TABLE DETACH忘记设置statement_timeout
参数而长时间锁表。
在PG 14版本之前,执行alter table pt_table detach partition part_name;
命令会阻塞该分区表(及父表)的所有操作,包括SELECT操作,这是非常严重的。从PG14开始,加上CONCURRENTLY后,就不再阻塞其它会话了,只是其它会话会报错而已。
模拟过程:
1、新显式开启事务,插入一条数据,别提交
2、执行alter table pt_table detach partition part_name;
3、新开事务,做任何操作都哈卡住
PG 14的语法为:
1 2 | ALTER TABLE [ IF EXISTS ] name DETACH PARTITION partition_name [ CONCURRENTLY | FINALIZE ] |
1)DETACH PARTITION时允许非阻塞方式进行detach
2)新增加了FINALIZE选项
在2个运行的事务中,允许一个分区从他的分区表中分离而不阻塞当前查询。因为在2个事务中运行,所以不能在一个事务块中使用。如果第2个事务取消或发生崩溃,则有ALTER TABLE...DETACH PARTITION...FINALIZE,执行最后的步骤。
- 在PG14中,上述语句分离目标表的指定分区,分离的分区继续作为一个独立的表存在,但不再与分离它的表有任何联系;
- 附加到目标表的索引的任何索引都被分离;
- 任何作为目标表中触发器的克隆创建的触发器都将被删除;
- 在外键约束中引用此分区表的任何表上都可以获得share lock;
- 如果指定CONCURRENTLY,它会使用降低的锁级别运行以避免阻塞可能正在访问分区表的其他会话,在这种模式下,内部使用两个事务。
在第一个事务期间,父表和分区都有SHARE UPDATE EXCLUSIVE锁,并将分区标记为正在分离(undergoing detach);此时,事务被提交,所有其他使用分区表的事务都将被等待。
一旦所有这些事务完成,第二个事务在分区表上获得SHARE UPDATE EXCLUSIVE锁,在分区上获得ACCESS EXCLUSIVE锁,分离过程完成。从分区约束复制而来的约束被添加到分区 - CONCURRENTLY不能运行在事务块中,也不能用于分区表含有默认分区的条件下。
- 如果FINALIZE被指定,前一次被中断或者被取消的detach concurrently调用会被完成。
- 在一个分区表中,一次只能有一个partition被detach。
实验过程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | /*创建测试表*/ create table test_list(id serial, flag text, location text, create_date date) partition by list(flag); -- 利用匿名块快速创建多个list分区子表 do $$ declare base text; sqlstring text; i int; begin base = 'create table test_list_%s partition of test_list for values in (''%s'')'; for i in 0..9 loop sqlstring = format(base, 'flag' || i, 'flag' || i); --raise notice '%', sqlstring; execute sqlstring; end loop; end $$language plpgsql; \d+ test_list alter table test_list detach partition test_list_flag0 concurrently ; -- 单独窗口开启事务 psql -U postgres -h172.18.0.14 -p 54327 -c lhrdb begin; INSERT INTO test_list(flag) VALUES('flag3'); -- 继续测试 alter table test_list detach partition test_list_flag3 concurrently; alter table test_list detach partition test_list_flag2; -- 当CONCURRENTLY被取消后,detach的分区表会显示DETACH PENDING, 需要使用ALTER TABLE … DETACH PARTITION … FINALIZE去完成被取消的DETACH, 才能去detach其它分区。 alter table test_list detach partition test_list_flag3 finalize; -- 可以再开一个窗口测试加和不加concurrently时的效果 -- 以下为加了concurrently时的效果 postgres=# INSERT INTO test_list(flag) VALUES('flag1'); ERROR: no partition of relation "test_list" found for row DETAIL: Partition key of the failing row contains (flag) = (flag1). postgres=# select * from test_list_flag1; id | flag | location | create_date ----+-------+----------+------------- 6 | flag1 | | (1 row) -- 以下为没有加concurrently时的效果,会卡住 postgres=# INSERT INTO test_list(flag) VALUES('flag1'); |
参考:https://www.xmmup.com/pg-14xintexingzhialter-table-detach-zhichi-concurrently.html
LZ4压缩可以用于TOAST数据
在 PostgreSQL 中,TOAST(The Oversized-Attribute Storage Technique)是一种用于处理大型或超过特定大小限制的数据的存储技术。它主要应用于存储行或列中的大型数据值,例如大型文本(
text
)、二进制数据(bytea
)或变长字符数据(varchar
)等。TOAST 数据是被自动压缩和分解的大型数据值。当某个数据值的大小超过 TOAST 阈值(默认为 2KB),PostgreSQL 将自动将其转换为 TOAST 格式,并将其存储在特殊的 TOAST 表中。原始表中存储的是 TOAST 数据的引用(指针),而不是实际的数据内容。这样可以节省存储空间并提高性能。
TOAST 数据的主要特点如下:
- 压缩存储:TOAST 数据会经过压缩处理,以减少所需的存储空间。
- 分解存储:如果一个数据值超过 TOAST 阈值,它将被分解成多个块,并存储在 TOAST 表中。这样可以提高读取和处理大型数据的效率。
- 透明访问:PostgreSQL 在访问 TOAST 数据时会自动处理数据的压缩和分解,对应用程序而言是透明的,无需显式的操作。
- 数据一致性:TOAST 数据的存储和访问过程会保持数据的一致性,即使是在并发环境下也能正确处理。
TOAST 技术使得 PostgreSQL 能够有效地存储和处理大型数据值,同时对应用程序开发者来说是透明的,无需特别的处理。
LZ4压缩可以用于TOAST数据:可以在列级别设置或者通过default_toast_compression设置默认值,编译时必须--with-lz4编译。默认仍是PGLZ;LZ4的压缩性能比PGLZ更好,使用更少CPU。测试表明,性能可以提升2倍以上,空间大小仅比PGLZ稍大。
PG 14为TOAST列添加了LZ4压缩,同时保留对pglz压缩的支持。toast列的压缩算法在默认情况下,是由default_toast_compression配置参数决定的,该配置参数值默认为pglz,可以使用alter table tab_name ALTER COLUMN c2 SET COMPRESSION lz4;
进行压缩算法的修改,但是修改对已经存在的tuple是无效的。
在CREATE TABLE (LIKE) 语句中,指定’LIKE table name INCLUDING COMPRESSION’子句会复制源表的压缩定义。pg_attribute系统表的attcompression列存储了压缩的定义,p代表pglz,l代表LZ4,可以使用pg_column_compression
函数去查看使用的压缩方法。
PG中,页是存储数据的单位,默认是8KB。一般情况下,一行数据不允许跨页存储。然而,有一些变长的数据类型,存储的数据可能超出一页大小。为了克服整个限制,大字段域会被压缩或者分割成多个物理行。这个技术就是TOAST:
https://www.postgresql.org/docs/14/storage-toast.html
默认情况下,如果表中有变长列,行数据的大小超过TOAST_TUPLE_THRESHOLD(默认2KB)就会触发TOAST。首先,会先压缩数据;压缩后如果仍然太大,会溢出存储。需要注意,如果列的存储策略指定EXTERNAL/PLAIN,压缩会被禁止。
PG14之前版本,TOAST仅支持一个压缩算法PGLZ(PG内置算法)。但是其他压缩算法可能比PGLZ更快或者有更高的压缩率。PG14中有了新压缩选项LZ4压缩,这是一个以速度著称的无损压缩算法。因此我们可以期望它有助于提高TOAST压缩和解压缩的速度。
与PGLZ相比,LZ4压缩和解压缩TOAST数据更加高效,并提供很好的性能。和未压缩数据相比,查询速度几乎一样,和PGLZ相比,插入快80%。当然某些场景下压缩率不太好,但如过你想要提升执行速度,强烈推荐使用LZ4算法。
同样需要注意,需要考虑表中的数据是否合适压缩。如果压缩率不好,它仍然会尝试压缩数,然后放弃。这将导致额外的内存资源浪费,并极大影响插入数据的速度。
安装操作系统包:
1 2 | yum install lz4 yum install lz4-devel |
PG14编译安装时需要带 —with-lz4编译选项,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | lhrdb=# SELECT * FROM pg_config ; name | setting -------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- BINDIR | /usr/lib/postgresql/14/bin DOCDIR | /usr/share/doc/postgresql-doc-14 HTMLDIR | /usr/share/doc/postgresql-doc-14 INCLUDEDIR | /usr/include/postgresql PKGINCLUDEDIR | /usr/include/postgresql INCLUDEDIR-SERVER | /usr/include/postgresql/14/server LIBDIR | /usr/lib/x86_64-linux-gnu PKGLIBDIR | /usr/lib/postgresql/14/lib LOCALEDIR | /usr/share/locale MANDIR | /usr/share/postgresql/14/man SHAREDIR | /usr/share/postgresql/14 SYSCONFDIR | /etc/postgresql-common PGXS | /usr/lib/postgresql/14/lib/pgxs/src/makefiles/pgxs.mk CONFIGURE | '--build=x86_64-linux-gnu' '--prefix=/usr' '--includedir=${prefix}/include' '--mandir=${prefix}/share/man' '--infodir=${prefix}/share/info' '--sysconfdir=/etc' '--localstatedir=/var' '--disable-option-checking' '--disable-silent-rules' '--libdir=${prefix}/lib/x86_64-linux-gnu' '--runstatedir=/run' '--disable-maintainer-mode' '--disable-dependency-tracking' '--with-tcl' '--with-perl' '--with-python' '--with-pam' '--with-openssl' '--with-libxml' '--with-libxslt' '--mandir=/usr/share/postgresql/14/man' '--docdir=/usr/share/doc/postgresql-doc-14' '--sysconfdir=/etc/postgresql-common' '--datarootdir=/usr/share/' '--datadir=/usr/share/postgresql/14' '--bindir=/usr/lib/postgresql/14/bin' '--libdir=/usr/lib/x86_64-linux-gnu/' '--libexecdir=/usr/lib/postgresql/' '--includedir=/usr/include/postgresql/' '--with-extra-version= (Debian 14.7-1.pgdg110+1)' '--enable-nls' '--enable-thread-safety' '--enable-debug' '--enable-dtrace' '--disable-rpath' '--with-uuid=e2fs' '--with-gnu-ld' '--with-gssapi' '--with-ldap' '--with-pgport=5432' '--with-system-tzdata=/usr/share/zoneinfo' 'AWK=mawk' 'MKDIR_P=/bin/mkdir -p' 'PROVE=/usr/bin/prove' 'PYTHON=/usr/bin/python3' 'TAR=/bin/tar' 'XSLTPROC=xsltproc --nonet' 'CFLAGS=-g -O2 -fstack-protector-strong -Wformat -Werror=format-security -fno-omit-frame-pointer' 'LDFLAGS=-Wl,-z,relro -Wl,-z,now' '--enable-tap-tests' '--with-icu' '--with-llvm' 'LLVM_CONFIG=/usr/bin/llvm-config-11' 'CLANG=/usr/bin/clang-11' '--with-lz4' '--with-systemd' '--with-selinux' 'build_alias=x86_64-linux-gnu' 'CPPFLAGS=-Wdate-time -D_FORTIFY_SOURCE=2' 'CXXFLAGS=-g -O2 -fstack-protector-strong -Wformat -Werror=format-security' CC | gcc CPPFLAGS | -Wdate-time -D_FORTIFY_SOURCE=2 -D_GNU_SOURCE -I/usr/include/libxml2 CFLAGS | -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Werror=vla -Wendif-labels -Wmissing-format-attribute -Wimplicit-fallthrough=3 -Wcast-function-type -Wformat-security -fno-strict-aliasing -fwrapv -fexcess-precision=standard -Wno-format-truncation -Wno-stringop-truncation -g -g -O2 -fstack-protector-strong -Wformat -Werror=format-security -fno-omit-frame-pointer CFLAGS_SL | -fPIC LDFLAGS | -Wl,-z,relro -Wl,-z,now -L/usr/lib/llvm-11/lib -Wl,--as-needed LDFLAGS_EX | LDFLAGS_SL | LIBS | -lpgcommon -lpgport -lselinux -llz4 -lxslt -lxml2 -lpam -lssl -lcrypto -lgssapi_krb5 -lz -lreadline -lpthread -lrt -ldl -lm VERSION | PostgreSQL 14.7 (Debian 14.7-1.pgdg110+1) (23 rows) lhrdb=# lhrdb=# CREATE TABLE compress1(c1 INT, c2 TEXT COMPRESSION lz4) ; CREATE TABLE lhrdb=# show default_toast_compression ; default_toast_compression --------------------------- pglz (1 row) lhrdb=# SET default_toast_compression=lz4; SET lhrdb=# show default_toast_compression ; default_toast_compression --------------------------- lz4 (1 row) lhrdb=# \d+ compress1 Table "public.compress1" Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description --------+---------+-----------+----------+---------+----------+-------------+--------------+------------- c1 | integer | | | | plain | | | c2 | text | | | | extended | lz4 | | Access method: heap lhrdb=# ALTER TABLE compress1 ALTER COLUMN c2 SET COMPRESSION pglz ; ALTER TABLE lhrdb=# \d+ compress1 Table "public.compress1" Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description --------+---------+-----------+----------+---------+----------+-------------+--------------+------------- c1 | integer | | | | plain | | | c2 | text | | | | extended | pglz | | Access method: heap |
其它示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | CREATE TABLE tbl (id int, col1 text COMPRESSION pglz, col2 text COMPRESSION lz4, col3 text); \d+ tbl lhrdb=# CREATE TABLE tbl (id int, lhrdb(# col1 text COMPRESSION pglz, lhrdb(# col2 text COMPRESSION lz4, lhrdb(# col3 text); CREATE TABLE lhrdb=# lhrdb=# \d+ tbl Table "public.tbl" Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description --------+---------+-----------+----------+---------+----------+-------------+--------------+------------- id | integer | | | | plain | | | col1 | text | | | | extended | pglz | | col2 | text | | | | extended | lz4 | | col3 | text | | | | extended | | | Access method: heap |
我们使用\d+命令可以看到所有列的压缩算法。如果列不支持或者没有指定压缩算法,那么会在Compression列显示空格。上面的例子中,id列不支持压缩算法,col1列使用PGLZ,col2使用LZ4,col3没有指定压缩算法,那么它会使用默认的压缩算法。
可以通过ALTER TABLE修改列压缩算法,但需要注意,修改后的算法仅影响执行整个命令后的insert数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | INSERT INTO tbl VALUES (1, repeat('abc',1000), repeat('abc',1000),repeat('abc',1000)); ALTER TABLE tbl ALTER COLUMN col1 SET COMPRESSION lz4; INSERT INTO tbl VALUES (2, repeat('abc',1000), repeat('abc',1000),repeat('abc',1000)); SELECT id, pg_column_compression(id) AS compression_colid, pg_column_compression(col1) AS compression_col1, pg_column_compression(col2) AS compression_col2, pg_column_compression(col3) AS compression_col3 FROM tbl; id | compression_colid | compression_col1 | compression_col2 | compression_col3 ---+-------------------+------------------+------------------+------------------ 1 | | pglz | lz4 | lz4 2 | | lz4 | lz4 | lz4 (2 rows) |
可以看到在修改压缩算法前插入的行,col1仍使用PGLZ压缩算法,即使将压缩算法从PGLZ修改到了LZ4。(那么,修改后进行解压时使用哪个算法呢?)
需要注意,如果从其他表扫数据插入本表,例如CREATE TABLE ...AS...或者INSERT INTO...SELECT...,插入的数据使用的压缩算法仍然使用原始数据的压缩方法。pg_dump和pg_dumpall也添加了选项--no-toast-compuression,使用整个选项后,不会dump出TOAST压缩选项。
新增视图pg_stat_wal,跟踪wal日志的生成与写入磁盘情况
1 2 3 4 5 6 7 8 9 10 11 12 13 | postgres=# \x Expanded display is on. postgres=# SELECT * FROM pg_stat_wal ; -[ RECORD 1 ]----+------------------------------ wal_records | 1009479 wal records的总数量 wal_fpi | 36 wal full page images的总数量 wal_bytes | 103725567 生成的wal的总大小 wal_buffers_full | 9889 由于wal buffers被写满而导致的wal data被写入disk的次数。 wal_write | 9927 wal buffers被写入的次数 wal_sync | 41 wal file 被同步到disk的次数 wal_write_time | 0 写wal buffers耗费的总时间 wal_sync_time | 0 sync wal files耗费的总时间 stats_reset | 2021-05-21 22:23:21.059275+08 本统计信息最后一次被重置的时间。 |
pg_prepared_statements新增统计软/硬解析次数
pg_prepared_statements新增统计软/硬解析次数。PostgreSQL中prepare statement可以用来cache plan,用来减少plan的次数。默认是前5次调用生成custom plan,然后生成generic plan。PG14中在pg_prepared_statements视图中新增了generic_plans和custom_plans两列,用来统计generic plan和custom plan的次数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | lhrdb=>PREPARE pr1 AS SELECT * FROM pg_class WHERE relname = $1; PREPARE lhrdb=>EXECUTE pr1('t1'); lhrdb=>select * from pg_prepared_statements; name | statement | prepare_time | parameter_types | from_sql | generic_plans | custom_plans ------+--------------------------------------------------+-------------------------------+-----------------+----------+---------------+-------------- pr1 | PREPARE pr1 AS SELECT * FROM pg_class WHERE +| 2021-05-13 10:17:28.429238+08 | {name} | t | 0 | 1 | relname = $1; | | | | | (1 row) -- 执行多次后再查看: lhrdb=>select * from pg_prepared_statements; -[ RECORD 1 ]---+------------------------------------------------- name | pr1 statement | PREPARE pr1 AS SELECT * FROM pg_class WHERE + | relname = $1; prepare_time | 2021-05-13 10:17:28.429238+08 parameter_types | {name} from_sql | t generic_plans | 2 custom_plans | 5 |
PostgreSQL 14新特性--减少索引膨胀
允许添加 btree 索引以删除过期的索引条目以防止页面拆分,有利于减少索引列频繁更新的表上的索引膨胀。Btree索引“自底向上”(Bottom-up index tuple deletion)的索引条目去除功能,在即将发生索引页分裂之前删除指向死元组的索引项。这可以减少索引条目的数量,避免昂贵的页面分割,以及稍后VACUUM清理时会出现的膨胀。
PG12中索引的存储更加高效,PG13添加索引条目去重功能进一步提升存储效率。PG14将带来“自底向上”的索引条目去除功能,旨在减少不必要的页面分裂、索引膨胀和更新大量索引带来的碎片。
为什么会出现索引膨胀
对于B-tree索引,表中每个行版本都有一个未死的索引条目(对所有人可见)。执行vacuum删除死记录时,也会删除对应的索引条目。和表一样,同样会在索引页中创建空的空间。这样的空间可以重用,但是如果没有新元组插入该页,这样的空间会保持为空。
这种膨胀在某种程度上是不可避免的,也是正常的。但如果膨胀太多,索引效率就会降低:
1) 对于索引范围扫描,必须扫描更多的页
2) RAM中缓存了索引页,意味着缓冲膨胀,就是浪费了RAM
3) 每个页中更少的索引条目意味着更少的“fan out”,索引树的层级将更高
如果频繁更新相同行,就会发生这种情况。VACUUM清理老元组前,表和索引会维护相同行的很多版本。如果索引页填满,将令人很烦:然后PG会将索引页分裂成2个。这是一个昂贵的操作,VACUUM执行完清理,我们最终会得到2个臃肿的页面而不是一个。
当前用于改善索引膨胀和性能的特性
HOT元组
HOT元组的创建可能是PG对抗索引中不必要条目的强大武器。使用此功能UPDATE创建产生的元组不会被索引条目引用,它还会引用元组的老版本。通过这种方法,不需要创建新的索引条目,可以避免索引膨胀。HOT参考:
https://www.cybertec-postgresql.com/en/hot-updates-in-postgresql-for-better-performance/
杀死索引条目
当索引扫描遇到一个指向死元组的条目时,标记该条目“killed”。后续索引扫描会在VACUUM删除他们之前跳过这些条目。此外,PG可以在索引页面已满时删除这样的条目,以避免页分裂。
PG14如何进一步减少索引膨胀
自下而上的索引元组删除比之前方法更进一步:他在索引页分裂即将发生前就删除指向死元组的索引条目。这可以减少索引条目的数量并避免昂贵的分裂,以及稍后VACUUM清理参数的膨胀。