Postgres 已经内置了数千个函数,但一些常见操作仍然需要拼接表达式或调用外部工具。Postgres 19 针对这些细小却高频的摩擦做了两类改进:直接生成指定区间内的随机日期与时间,以及通过 SQL 获取角色、数据库和表空间等全局对象的完整 DDL。
这些能力未必会改变查询优化器或存储引擎,却能明显简化测试数据生成、配置审计和运维脚本。需要注意的是,Postgres 19 尚处于开发周期,正式发布前应以对应版本文档和实际函数签名为准。
随机日期不再需要手工计算区间
过去要生成 2026 年内的随机日期,通常需要从年初开始加上一段随机 interval:
SELECT (
DATE '2026-01-01'
+ random() * INTERVAL '364 days'
)::date;
这段表达式可以工作,但存在几个不够直观的地方:
date + interval会把结果提升为timestamp,所以还要显式转回date。- 天数需要人工计算,闰年尤其容易产生边界错误。
- 上界是否能够取到并不直观。
- 如果目标类型换成
timestamp with time zone,还得重新调整类型转换。
Postgres 19 为 random() 增加了时间类型的区间重载。可以把同类型的下界和上界直接传进去:
SELECT random(
DATE '2026-01-01',
DATE '2026-12-31'
) AS random_order_date;
输入是 date,输出仍然是 date。同样的模式也适用于另外两种常用时间类型:
SELECT random(
TIMESTAMP '2026-01-01 00:00:00',
TIMESTAMP '2026-12-31 23:59:59'
) AS random_local_time;
SELECT random(
TIMESTAMPTZ '2026-01-01 00:00:00+00',
TIMESTAMPTZ '2026-12-31 23:59:59+00'
) AS random_global_time;
显式边界让代码更容易审查,也避免了 interval 运算带来的类型变化。调用者只需要决定业务区间和时间类型,不必再维护天数换算逻辑。
用固定种子生成可复现的测试数据
随机数据只有在能够重现时,才适合进入自动化测试。Postgres 的 setseed() 可以固定当前会话中的伪随机序列。种子取值范围为 -1.0 到 1.0;在新会话中使用相同种子和相同语句顺序,可以得到相同的随机序列。
下面是一段可以在 Postgres 19 环境中直接运行或改造的测试数据脚本:
DROP TABLE IF EXISTS test_orders;
CREATE TABLE test_orders (
order_id bigint PRIMARY KEY,
customer_id bigint NOT NULL,
ordered_on date NOT NULL,
amount numeric(10, 2) NOT NULL
);
SELECT setseed(0.314159);
INSERT INTO test_orders (order_id, customer_id, ordered_on, amount)
SELECT
n,
floor(random() * 500 + 1)::bigint,
random(DATE '2026-01-01', DATE '2026-12-31'),
round((random() * 990 + 10)::numeric, 2)
FROM generate_series(1, 10000) AS g(n);
SELECT
min(ordered_on) AS first_date,
max(ordered_on) AS last_date,
count(*) AS rows_created
FROM test_orders;
运行前只需要调整年份、客户数量和 generate_series 的规模。要制作百万行压测数据,可以把上限改成 1000000,但应同时评估 WAL、磁盘空间、索引维护和复制延迟。
还有一个容易忽略的边界:setseed() 适合重现测试数据,不是密码学随机源。令牌、密码、会话密钥等安全数据仍应由专门的安全随机机制生成。
全局对象的 DDL 终于能留在 SQL 里
角色、数据库和表空间属于集群级对象,不依附于某一个数据库。以往如果需要精确还原它们的定义,通常要运行 pg_dumpall,然后从集群级输出中过滤目标对象。
这种方式有几个工程问题:它依赖 shell 和外部二进制,客户端版本需要与服务端兼容,输出也无法自然地参与 SQL 的过滤、连接、存储和审计流程。
Postgres 19 增加了三类 DDL 信息函数,用于查询这些全局对象:
pg_get_roledef():返回角色定义及相关属性。pg_get_tablespacedef():返回表空间定义,包括位置和存储设置。pg_get_databasedef():返回数据库定义。
可以这样实践角色定义查询:
CREATE ROLE report_reader
NOLOGIN
CONNECTION LIMIT 8;
ALTER ROLE report_reader SET statement_timeout = '30s';
SELECT pg_get_roledef('report_reader');
与从整个集群转储中过滤文本相比,这种调用有三个直接收益:查询对象明确、结果可以作为 SQL 值处理、执行过程不需要数据库服务器上的 shell 权限。
出于安全考虑,角色定义函数不会在普通查询结果中暴露密码散列。这个限制很重要:可查询 DDL 不应变成低权限用户提取认证材料的渠道。实际部署时仍需检查函数执行权限,并限制谁能查看敏感的集群元数据。
数据库和表空间也采用相同思路:
SELECT pg_get_databasedef('analytics');
SELECT pg_get_tablespacedef('fast_storage');
这些查询要求目标对象已经存在,并且调用者拥有足够权限。表空间定义可能包含服务器文件系统路径,审计结果应当按照运维敏感数据管理。
把 DDL 查询接入配置审计
函数返回值可以参与普通 SQL,这是相对外部转储工具最实用的变化。可以这样实践一个最小审计表,定期记录全局对象定义:
CREATE TABLE IF NOT EXISTS global_ddl_audit (
captured_at timestamptz NOT NULL DEFAULT clock_timestamp(),
object_type text NOT NULL,
object_name text NOT NULL,
object_ddl text NOT NULL
);
INSERT INTO global_ddl_audit (object_type, object_name, object_ddl)
SELECT
'role',
rolname,
pg_get_roledef(rolname)
FROM pg_roles
WHERE rolname = 'report_reader';
SELECT captured_at, object_name, object_ddl
FROM global_ddl_audit
ORDER BY captured_at DESC;
生产环境可以在此基础上增加变更哈希、操作者、工单编号和保留期限。审计表本身应设置严格权限,因为 DDL 可能暴露角色关系、表空间路径和数据库配置。
这组函数目前也不是 pg_dump 或 pg_dumpall 的完整替代品。它们解决的是全局对象的定向查询,而不是完整备份、依赖排序、数据导出或跨集群恢复。Postgres 早已有查询函数、触发器、索引和视图定义的能力,但尚未形成覆盖所有对象类型的统一内省接口。
升级时该怎样采用
评估 Postgres 19 时,可以优先检查以下事项:
- 将手工拼接的随机日期表达式替换为带类型边界的
random(),并补充上下界测试。 - 在测试脚本开头调用
setseed(),确保失败样本能够稳定复现。 - 用新的 DDL 函数简化角色、数据库和表空间的定向检查,但继续保留正式备份流程。
- 审查 DDL 查询和审计表权限,尤其注意角色关系、数据库选项及表空间路径。
- 在正式迁移前核对 Postgres 19 最终文档;开发版本中的函数名称、选项和输出格式仍可能调整。
这些改进的价值不在于增加复杂能力,而在于删除不必要的仪式:随机日期变成一个类型清晰的函数调用,全局对象定义也能直接进入 SQL 工作流。对于需要维护测试夹具、配置基线和数据库审计的团队,这类小改动通常会很快转化为更短、更可靠的脚本。