一、pg_resetwal简介
pg_resetwal 工具用来重置PostgreSQL 数据库集群的预写日志和其他控制信息,适用于pg12之后的版本。
1.描述
pg_resetwal
清除预写日志(WAL),并可选地重置pg_control文件中的一些其他控制信息。当 WAL
文件或pg_control
控制文件损坏时,导致数据库无法启动时,该操作将作为数据库修复的最后手段使用。
通过 pg_resetwal
修复而启动数据库后,可能会由于部分提交的事务,导致数据库可能存在数据不一致的情况。所以,应该立即转储数据,建议重新初始化新的数据库恢复。恢复后再检查不一致,并根据需要进一步修复。
pg_resetwal
只能由安装数据库用户运行,因为它需要对数据目录进行读/写访问。注意:考虑安全原因,pg_resetwal
不使用环境变量 PGDATA,所以必须在命令行上指定数据目录。
如果 pg_resetwal 提示无法确定pg_control的有效数据,可以通过指定-f(force)选项强制继续执行。大多数字段可以自动匹配,但下一个OID、下一个事务ID和epoch、下一多事务ID和偏移量以及WAL起始位置字段值可能需要手动指定。可以使用一些选项设置这些字段值。如果无法确定这些字段的正确值,也可使用-f,但必须对恢复的数据库更为严谨的处理:必须立即转储和恢复。在转储之前,不要在数据库中执行任何数据修改操作,因为任何此类操作都可能会使损坏更严重。
2.选项详解
[pg14@node1 pg_wal]$ pg_resetwal --help
pg_resetwal resets the PostgreSQL write-ahead log.
Usage:
pg_resetwal [OPTION]... DATADIR
Options:
-c, --commit-timestamp-ids=XID,XID
set oldest and newest transactions bearing
commit timestamp (zero means no change)
[-D, --pgdata=]DATADIR data directory
-e, --epoch=XIDEPOCH set next transaction ID epoch
-f, --force force update to be done
-l, --next-wal-file=WALFILE set minimum starting location for new WAL
-m, --multixact-ids=MXID,MXID set next and oldest multitransaction ID
-n, --dry-run no update, just show what would be done
-o, --next-oid=OID set next OID
-O, --multixact-offset=OFFSET set next multitransaction offset
-u, --oldest-transaction-id=XID set oldest transaction ID
-V, --version output version information, then exit
-x, --next-transaction-id=XID set next transaction ID
--wal-segsize=SIZE size of WAL segments, in megabytes
-?, --help show this help, then exit
Report bugs to <pgsql-bugs@lists.postgresql.org>.
PostgreSQL home page: <https://www.postgresql.org/>
(1) -c
-c xid1,xid2
--commit-timestamp-ids=xid1,xid2
只有当 pg_resetwal 无法通过读取pg_control 来确定适当的值时,才需要以下选项。对于采用数字参数的值,可以使用前缀0x指定十六进制值。手动设置可以检索提交时间的最旧和最新事务ID。
xid1:可以通过在 $PGDATA/pg_commit_ts
目录中查找数值最小的文件名来确定可以检索提交时间的最旧事务ID的安全值(第一部分)。xid2:相反,可以通过在同一目录中查找数值最大的文件名来确定可以检索提交时间的最新事务ID的安全值。文件名为十六进制。
(2) -e
-e xid_epoch
--epoch=xid_epoch
手动设置下一个事务ID的epoch。
事务ID epoch实际上并没有保存在数据库中的任何位置,除非存储在由pg_resetwal设置的字段中,因此就数据库本身而言,任何值都是有效的。您可能需要调整此值,以确保复制系统(如Slony-I和Skytools)正常工作-如果是这样,应该可以从下游复制数据库的状态获得适当的值。
(3)-l
-l walfile
--next-wal-file=walfile
通过指定下一个WAL段文件的名称,手动设置WAL起始位置。
可以通过 $PGDATA/pg_wal目录中查找数值最新的WAL段的文件名,+1。这些名称也是十六进制的,有三个部分。第一部分是“时间线ID”,通常应保持不变。例如,如果0000000100000009000000B7是pg_wal中最大的条目,请使用
-l 0000000100000009000000B8 或更高。
注意,当使用非默认WAL段大小时,WAL文件名中的数字与系统函数和系统视图报告的LSN不同。此选项采用WAL文件名,而不是LSN。
格式:-l 0000000100000009000000B8
(4) -m
-m mxid1,mxid2
--multixact-ids=mxid1,mxid2
手动设置下一个和最旧的多事务ID。
mxid1:下一个多事务ID的安全值可以通过在 $PGDATA/pg_multixact/offsets目录中查找数值最大的文件名,+1,然后乘以65536(0x10000)来确定。mxid2:相反,最旧的多事务ID的安全值可以通过 $PGDATA/pg_multixact/offsets
目录中数字最小的文件名,乘以65536来确定。文件名是十六进制的,因此最简单的方法是以十六进制指定选项值并附加四个零。
注意:若当前值为0,那么mxid2亦为0,则mxid2取mxid1的值。
格式:-m 0x10000, 0x100000
(5) -o
-o oid
--next-oid=oid
手动设置下一个OID。
没有相对简单的方法来确定下一个超出数据库中最大OID的OID,但幸运的是,正确设置下一个OID并不重要。
(6) -O
-O mxoff
--multixact-offset=mxoff
手动设置下一个多事务处理偏移量。
安全值可以通过在 $PGDATA/pg_multixact/members
目录中查找数值最大的文件名,+1,然后乘以52352(0xCC80)来确定。文件名为十六进制。没有像附加零的其他选项那样的简单方法。
若找到最大值0000,+1,乘以 52352 (0xCC80),这个需要进行计算,没有简单的加0的方法。格式:0xCC80
若找到最大值00C1(193),+1,乘以 52352 (0xCC80)。最后得值:(193+1)* 52352 = 4918288。16进制为:4B0C10,结果为:0x004B0C10
(7) --wal-segsize=wal_segment_size
设置新的WAL段大小(MB)。该值必须设置为介于1和1024(兆字节)之间的2的幂。
虽然pg_resetwal会将WAL起始地址设置在最新的现有WAL段文件之外,但某些段大小的更改可能会导致重用以前的WAL文件名。如果WAL文件名重叠会导致归档策略出现问题,建议将-l与此选项一起使用以手动设置WAL起始地址。
(8) -u
-u xid
--oldest-transaction-id=xid
手动设置最旧的未冻结交易ID。
可以通过在 $PGDATA/pg_xact
目录中查找数值最小的文件名,然后乘以1048576(0x100000)来确定安全值。请注意,文件名是十六进制的,因此最简单的方法是以十六进制指定选项值并附加五个零。例如,如果0007是pg_xact中最小的条目,-u 0x700000将起作用。
(9) -x
-x xid
--next-transaction-id=xid
手动设置下一个事务ID。
安全值可以通过在 $PGDATA/pg_xact
目录中查找数值最大的文件名,+1,然后乘以1048576(0x100000)来确定。请注意,文件名是十六进制的。,因此最简单的方法是以十六进制指定选项值并附加五个零。例如,如果0011是pg_xact中最大的条目,-x 0x1200000将起作用(五个尾随零提供正确的乘数)。
二、注意事项
pg_resetwal
用于丢失一些文件导致数据库无法启动进行修复。需要再次强调,pg_resetwal
并不是日常使用的工具,是数据库最后的修复手段。常规性恢复请使用常规手段进行。
使用 pg_resetwal
修复的数据库,一般会正常启动,但是可能会因为参数的不准确而导致数据库存在其他异常问题。
如果因为丢失WAL文件导致数据库不一致,或者控制文件丢失,那么我们只需要通过这个工具生成一个最新的文件就可以强制打开数据库了。
三、案例:重置WAL
1.创建测试数据
test2=# create table t2(id int,name varchar(10));
CREATE TABLE
test2=# insert into t2 values(1,'aaa');
INSERT 0 1
test2=# insert into t2 values(2,'bbb');
INSERT 0 1
test2=# checkpoint;
CHECKPOINT
test2=# insert into t2 values(3,'ccc');
INSERT 0 1
2.模拟数据库异常
[pg14@node1 pgdata02]$ ps -ef | grep postgres
pg14 5267 10725 0 07:15 pts/1 00:00:00 grep --color=auto postgres
pg14 10329 17633 0 Aug17 ? 00:00:00 postgres: test2 test2 [local] idle
pg14 17633 1 0 Aug17 ? 00:00:00 /pgsoft/pg14/bin/postgres -D /pgdata02
pg14 17635 17633 0 Aug17 ? 00:00:00 postgres: checkpointer
pg14 17636 17633 0 Aug17 ? 00:00:00 postgres: background writer
pg14 17637 17633 0 Aug17 ? 00:00:00 postgres: walwriter
pg14 17638 17633 0 Aug17 ? 00:00:01 postgres: autovacuum launcher
pg14 17639 17633 0 Aug17 ? 00:00:01 postgres: stats collector
pg14 17640 17633 0 Aug17 ? 00:00:00 postgres: logical replication launcher
[pg14@node1 pgdata02]$ kill -9 17633
[pg14@node1 pgdata02]$ ps -ef | grep postgres
pg14 5309 10725 0 07:15 pts/1 00:00:00 grep --color=auto postgres
3.删除WAL日志
[pg14@node1 pg_wal]$ ls -lrt
total 16384
drwx------ 2 pg14 pg14 6 Aug 16 16:42 archive_status
-rw------- 1 pg14 pg14 16777216 Aug 18 07:14 000000010000000000000001
[pg14@node1 pg_wal]$ rm -rf 00000001000000000000000*
[pg14@node1 pg_wal]$ ls -lrt
total 0
drwx------ 2 pg14 pg14 6 Aug 16 16:42 archive_status
4.尝试启动数据库
[pg14@node1 pg_wal]$ pg_ctl start -D $PGDATA -l /tmp/logfile
waiting for server to start...... stopped waiting
pg_ctl: could not start server
Examine the log output.
数据库日志信息:
2023-08-18 07:19:10.451 CST [5527] LOG: starting PostgreSQL 14.6 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-36), 64-bit
2023-08-18 07:19:10.451 CST [5527] LOG: listening on IPv4 address "0.0.0.0", port 5666
2023-08-18 07:19:10.451 CST [5527] LOG: listening on IPv6 address "::", port 5666
2023-08-18 07:19:10.459 CST [5527] LOG: listening on Unix socket "/tmp/.s.PGSQL.5666"
2023-08-18 07:19:10.468 CST [5528] LOG: database system was interrupted; last known up at 2023-08-18 07:13:49 CST
2023-08-18 07:19:11.255 CST [5528] LOG: invalid primary checkpoint record
2023-08-18 07:19:11.256 CST [5528] PANIC: could not locate a valid checkpoint record
2023-08-18 07:19:12.621 CST [5527] LOG: startup process (PID 5528) was terminated by signal 6: Aborted
2023-08-18 07:19:12.621 CST [5527] LOG: aborting startup due to startup process failure
2023-08-18 07:19:12.666 CST [5527] LOG: database system is shut down
5.重置WAL日志
[pg14@node1 pg_wal]$ pg_resetwal $PGDATA
The database server was not shut down cleanly.
Resetting the write-ahead log might cause data to be lost.
If you want to proceed anyway, use -f to force reset.
[pg14@node1 pg_wal]$ pg_resetwal -f $PGDATA
Write-ahead log reset
[pg14@node1 pg_wal]$ ls -lrt
total 16384
drwx------ 2 pg14 pg14 6 Aug 16 16:42 archive_status
-rw------- 1 pg14 pg14 16777216 Aug 18 07:21 000000010000000000000002
6.启动数据库
[pg14@node1 pg_wal]$ pg_ctl start -D $PGDATA -l /tmp/logfile
waiting for server to start.... done
server started
数据库日志:
2023-08-18 07:22:38.525 CST [5756] LOG: starting PostgreSQL 14.6 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-36), 64-bit
2023-08-18 07:22:38.525 CST [5756] LOG: listening on IPv4 address "0.0.0.0", port 5666
2023-08-18 07:22:38.525 CST [5756] LOG: listening on IPv6 address "::", port 5666
2023-08-18 07:22:38.532 CST [5756] LOG: listening on Unix socket "/tmp/.s.PGSQL.5666"
2023-08-18 07:22:38.540 CST [5758] LOG: database system was shut down at 2023-08-18 07:21:57 CST
2023-08-18 07:22:38.549 CST [5756] LOG: database system is ready to accept connections
7.查看数据
[pg14@node1 pg_wal]$ psql -Utest2 -dtest2
psql (14.6)
Type "help" for help.
test2=# select * from t2;
id | name
----+------
1 | aaa
2 | bbb
(2 rows)
发现checkpoint后的数据由于WAL重置而丢失。
四、案例:重建控制文件
1.创建测试数据
test2=# create table t3(id int,name varchar(8));
CREATE TABLE
test2=# insert into t3 values(1,'aaa');
INSERT 0 1
test2=# insert into t3 values(2,'bbb');
INSERT 0 1
test2=# checkpoint;
CHECKPOINT
test2=# insert into t3 values(3,'ccc');
INSERT 0 1
test2=# select * from t3;
id | name
----+------
1 | aaa
2 | bbb
3 | ccc
(3 rows)
2.模拟数据库异常
[pg14@node1 pg_wal]$ ps -ef | grep postgres
pg14 5756 1 0 07:22 ? 00:00:00 /pgsoft/pg14/bin/postgres -D /pgdata02
pg14 5759 5756 0 07:22 ? 00:00:00 postgres: checkpointer
pg14 5760 5756 0 07:22 ? 00:00:00 postgres: background writer
pg14 5761 5756 0 07:22 ? 00:00:00 postgres: walwriter
pg14 5762 5756 0 07:22 ? 00:00:00 postgres: autovacuum launcher
pg14 5763 5756 0 07:22 ? 00:00:00 postgres: stats collector
pg14 5764 5756 0 07:22 ? 00:00:00 postgres: logical replication launcher
pg14 6331 10725 0 07:32 pts/1 00:00:00 grep --color=auto postgres
[pg14@node1 pg_wal]$ kill -9 5756
[pg14@node1 pg_wal]$ ps -ef | grep postgres
pg14 6374 10725 0 07:32 pts/1 00:00:00 grep --color=auto postgres
3.破坏控制文件
[pg14@node1 global]$ cd $PGDATA/global
[pg14@node1 global]$ ls -lrt pg_control
-rw------- 1 pg14 pg14 8192 Aug 18 07:30 pg_control
[pg14@node1 global]$ mv pg_control pg_control_bak
[pg14@node1 global]$ ls -lrt pg_control
ls: cannot access pg_control: No such file or directory
4.尝试启动数据库
[pg14@node1 global]$ pg_ctl start -D $PGDATA -l /tmp/logfile
pg_ctl: another server might be running; trying to start server anyway
waiting for server to start.... stopped waiting
pg_ctl: could not start server
Examine the log output.
数据库日志:
postgres: could not find the database system
Expected to find it in the directory "/pgdata02",
but could not open file "/pgdata02/global/pg_control": No such file or directory
5.重建控制文件
需要参数:
-l :可以通过 $PGDATA/pg_wal
目录中查找数值最新的WAL段的文件名,+1。
[pg14@node1 pgdata02]$ cd pg_wal
[pg14@node1 pg_wal]$ ll
total 16384
-rw------- 1 pg14 pg14 16777216 Aug 18 07:30 000000010000000000000002
drwx------ 2 pg14 pg14 6 Aug 16 16:42 archive_status
[pg14@node1 pg_wal]$
-l 000000010000000000000003
-m:设置下一个和最旧的事务ID。
mxid1:下一个多事务ID的安全值可以通过在 $PGDATA/pg_multixact/offsets
目录中查找数值最大的文件名,+1,然后乘以65536(0x10000)来确定。mxid2:相反,最旧的多事务ID的安全值可以通过$PGDATA/pg_multixact/offsets
目录中数字最小的文件名,乘以65536来确定。文件名是十六进制的,因此最简单的方法是以十六进制指定选项值并附加四个零。
pg14@node1 pgdata02]$ cd pg_multixact/offsets/
[pg14@node1 offsets]$ ll
total 8
-rw------- 1 pg14 pg14 8192 Aug 18 07:27 0000
若当前值为0,那么mxid2亦为0,则mxid2取mxid1的值。
当前值为0000,则mxid1为:0x10000,mxid2取mxid1的值,则使用-m 0x10000,0x10000
-O:设置下一个多事务处理偏移量。
安全值可以通过在 $PGDATA/pg_multixact/members
目录中查找数值最大的文件名,+1,然后乘以52352(0xCC80)来确定。文件名为十六进制。没有像附加零的其他选项那样的简单方法。
[pg14@node1 members]$ ls -lrt
total 8
-rw------- 1 pg14 pg14 8192 Aug 16 16:42 0000
当前值为0000,则使用 -O 0xCC80
-x:设置下一个事务ID。安全值可以通过在$PGDATA/pg_xact目录中查找数值最大的文件名,+1,然后乘以1048576(0x100000)来确定。请注意,文件名是十六进制的。,因此最简单的方法是以十六进制指定选项值并附加五个零。
[postgres@lyp members]$ cd $PGDATA/pg_xact
[postgres@lyp pg_xact]$ ls -rlt
total 8
-rw-------. 1 postgres postgres 8192 Mar 13 17:08 0000
[postgres@lyp pg_xact]$
当前值为0000,则使用-x 0x100000
由于上次未正常关闭,存在PID文件,先删除pid文件
mv postmaster.pid postmaster.pid_bak
[pg14@node1 global]$ touch pg_control
[pg14@node1 global]$ pg_resetwal -l 000000010000000000000003 -m 0x10000,0x10000 -O 0xCC80 -x 0x100000 -f $PGDATA
pg_resetwal: warning: pg_control exists but is broken or wrong version; ignoring it
Write-ahead log reset
[pg14@node1 global]$
6.尝试启动数据库
[pg14@node1 global]$ pg_ctl start -D $PGDATA -l /tmp/logfile
waiting for server to start.... done
server started
数据库日志:
2023-08-18 07:52:00.805 CST [7540] LOG: starting PostgreSQL 14.6 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-36), 64-bit
2023-08-18 07:52:00.805 CST [7540] LOG: listening on IPv4 address "0.0.0.0", port 5666
2023-08-18 07:52:00.806 CST [7540] LOG: listening on IPv6 address "::", port 5666
2023-08-18 07:52:00.812 CST [7540] LOG: listening on Unix socket "/tmp/.s.PGSQL.5666"
2023-08-18 07:52:00.820 CST [7541] LOG: database system was shut down at 2023-08-18 07:50:25 CST
2023-08-18 07:52:00.826 CST [7540] LOG: database system is ready to accept connections
7.验证数据
[pg14@node1 global]$ psql -Utest2 -dtest2
psql (14.6)
Type "help" for help.
test2=# select * from t3;
id | name
----+------
1 | aaa
2 | bbb
(2 rows)
test2=#
可以看到测试数据中 checkpoint
检查点后的数据,由于控制文件重置而丢失。
五、使用限制
1、注意据库正在运行时,不得使用此命令。
2、如果 pg_resetwal
在数据目录中找到服务器锁定文件,将启动失败。
3、如果数据库已崩溃,那么可能会留下一个锁文件(postmaster.pid
);在这种情况下,可以删除锁定文件以保证postmaster.pid
的正常运行。但在此操作之前,需要再次确保没有数据库进程在运行。
4、pg_resetwal
仅适用于相同主版本的数据库服务器。