PostgreSQL 17: 使用 pg_stat_progress_copy 跟踪 COPY 操作跳过的行

John Doe 五月 26, 2025

你需要跟踪 COPY 操作跳过的行吗?现在,PostgreSQL 提供了 pg_stat_progress_copy 视图。

与 SQL 共舞的大象

特性提交日志

在 COPY FROM 操作中添加跳过元组的进度报告功能。

之前的提交已经支持了 COPY 命令跳过格式错误的数据,但此前无法查看 COPY FROM 过程中实际跳过的元组数量。

本次提交向 pg_stat_progress_copy 视图添加了新的 “tuples_skipped” 列,用于报告因包含格式错误的数据而被跳过的元组数量。

讨论:https://postgr.es/m/d12fd8c99adcae2744212cb23feff6ed%40oss.nttdata.com

示例

在之前的文章中,我们看到了,如何让 COPY 命令忽略任何不符合目标表格式的行。虽然这一功能很实用,但它也隐藏了一些您可能想了解的信息:加载过程中丢弃了多少行?本次提交针对这一问题,对 pg_stat_progress_copy 视图进行了扩展,新增了一个名为tuples_skipped的列。该列并非记录加载完成后跳过的行数,而是至少让您能够监控数据加载过程中已跳过的行数。

为了验证这一点,我们不能使用非常小的文本文件,因为数据加载速度会过快:

create table import ( a int, b varchar(4), c int, d varchar(4));

copy import from '/home/postgres/data.txt' with (delimiter ' ');

-- 在另一个会话中
select * from pg_stat_progress_copy ;
 pid | datid | datname | relid | command | type | bytes_processed | bytes_total | tuples_processed | tuples_excluded | tuples_skipped 
-----+-------+---------+-------+---------+------+-----------------+-------------+------------------+-----------------+----------------
(0 rows)

为了真正看到 pg_stat_progress_copy 中的某些内容,让我们来生成一个简单但更大的数据集:

create table t ( a varchar(50), b varchar(50) );
insert into t select md5(i::text), md5(i::text) from generate_series(1,3000000) i;
copy t to '/var/tmp/a';
$ head /var/tmp/a
c4ca4238a0b923820dcc509a6f75849b        c4ca4238a0b923820dcc509a6f75849b
c81e728d9d4c2f636f067f89cc14862c        c81e728d9d4c2f636f067f89cc14862c
eccbc87e4b5ce2fe28308fd9f2a7baf3        eccbc87e4b5ce2fe28308fd9f2a7baf3
a87ff679a2f3e71d9181a67b7542122c        a87ff679a2f3e71d9181a67b7542122c
e4da3b7fbbce2345d7772b0674a318d5        e4da3b7fbbce2345d7772b0674a318d5
1679091c5a880faf6fb5e6087eb1b2dc        1679091c5a880faf6fb5e6087eb1b2dc
8f14e45fceea167a5a36dedd4bea2543        8f14e45fceea167a5a36dedd4bea2543
c9f0f895fb98ab9159f51fd0297e236d        c9f0f895fb98ab9159f51fd0297e236d
45c48cce2e2d7fbdea1afc51c7c6ad26        45c48cce2e2d7fbdea1afc51c7c6ad26
d3d9446802a44259755d38e6d163e820        d3d9446802a44259755d38e6d163e820

这给了我们一个包含两个随机字符串列的简单文本文件。在加载之前,我们将引入一些表中无法容纳的行:

$ sed -i '1500000 i 123456789012345678901234567890123456789012345678901234567890' /var/tmp/a
$ sed -i '1500000 i 123456789012345678901234567890123456789012345678901234567890' /var/tmp/a
$ sed -i '1500000 i 123456789012345678901234567890123456789012345678901234567890' /var/tmp/a
$ sed -i '1500000 i 123456789012345678901234567890123456789012345678901234567890' /var/tmp/a
$ sed -i '1500000 i 123456789012345678901234567890123456789012345678901234567890' /var/tmp/a
$ grep 123456789012345678901234567890123456789012345678901234567890 /var/tmp/a
123456789012345678901234567890123456789012345678901234567890
123456789012345678901234567890123456789012345678901234567890
123456789012345678901234567890123456789012345678901234567890
123456789012345678901234567890123456789012345678901234567890
123456789012345678901234567890123456789012345678901234567890

这些 60 个字符的列无法容纳在第一列中,因此会加载失败。在加载之前,我们启动第二个会话并监视 pg_stat_progress_copy

postgres=# select datname, tuples_processed, tuples_skipped from pg_stat_progress_copy ;
 datname | tuples_processed | tuples_skipped 
---------+------------------+----------------
(0 rows)

postgres=# \watch 1 
 Thu 25 Jan 2025 01:08:31 PM CST (every 1s)
 
 datname | tuples_processed | tuples_skipped 
---------+------------------+----------------
(0 rows)
...

在第一个会话中,我们将创建一个与前一个表具有相同结构的新表并开始加载数据:

create table z ( like t );
copy z from '/var/tmp/a' with ( ON_ERROR 'ignore' );

在第二个会话中,我们会看到我们上面添加的五行被跳过了:

 datname | tuples_processed | tuples_skipped
---------+------------------+----------------
(0 rows)

 Thu 25 Jan 2025 01:11:18 PM CST (every 1s)

 datname  | tuples_processed | tuples_skipped
----------+------------------+----------------
 postgres |           163000 |              0
(1 row)

 Thu 25 Jan 2025 01:11:19 PM CST (every 1s)

 datname  | tuples_processed | tuples_skipped
----------+------------------+----------------
 postgres |          2346000 |              5
(1 row)

 Thu 25 Jan 2025 01:11:20 PM CST (every 1s)

 datname | tuples_processed | tuples_skipped
---------+------------------+----------------
(0 rows)

非常不错的特性,感谢所有参与的社区人员。

参考

提交日志:https://git.postgresql.org/pg/commitdiff/729439607ad210dbb446e31754e8627d7e3f7dda