前言
最近看到有人提了个问题,系统索引坏了,也无法登录进去,该如何处理呢?
如何处理损坏的系统表索引
这是你不希望在一个重要的PostgreSQL系统上看到的东西。
postgres@centos8pg:/home/postgres/ [pgdev] psql
psql: error: could not connect to server: FATAL: index "pg_class_oid_index" contains unexpected zero page at block 0
HINT: Please REINDEX it.
提示很清楚,你应该如何解决这个问题,但无论如何,让我们做一个简短的演示,说明如何做。
在PostgreSQL中,有多种情况可能导致索引或表的损坏。当然,这可能是PostgreSQL的BUG。可能是文件系统的问题,也可能是掉电引起的,存储系统不能再写入磁盘上的文件。
在这个小样例中,我们将看一下pg_class系统表上有哪些索引可用。
postgres=# select * from pg_indexes where tablename = 'pg_class';
schemaname | tablename | indexname | tablespace | indexdef
------------+-----------+-----------------------------------+------------+----------------------------------------------------------------------------------------------------------------
pg_catalog | pg_class | pg_class_oid_index | | CREATE UNIQUE INDEX pg_class_oid_index ON pg_catalog.pg_class USING btree (oid)
pg_catalog | pg_class | pg_class_relname_nsp_index | | CREATE UNIQUE INDEX pg_class_relname_nsp_index ON pg_catalog.pg_class USING btree (relname, relnamespace)
pg_catalog | pg_class | pg_class_tblspc_relfilenode_index | | CREATE INDEX pg_class_tblspc_relfilenode_index ON pg_catalog.pg_class USING btree (reltablespace, relfilenode)
以第一个为例,让我们通过向磁盘上的文件头写"零"来损坏索引。
postgres=# select pg_relation_filepath('pg_class_oid_index');
pg_relation_filepath
----------------------
base/12710/16386
(1 row)
postgres=# \! echo $PGDATA
/u02/pgdata/DEV
postgres=# \! dd if=/dev/zero of=/u02/pgdata/DEV/base/12710/16386 bs=8k count=2
2+0 records in
2+0 records out
16384 bytes (16 kB, 16 KiB) copied, 0.000226611 s, 72.3 MB/s
当我们需要的东西应该来自我们刚刚损坏的索引时,会发生什么?
postgres=# select oid from pg_class where oid = 123344;
ERROR: could not read block 2 in file "base/12710/16386": read only 0 of 8192 bytes
这显然无法工作,所以让我们应用PostgreSQL上面推荐的修复方法,reindex。
postgres=# reindex index pg_class_oid_index;
REINDEX
postgres=# select oid from pg_class where oid = 123344;
oid
-----
(0 rows)
在重建索引(会完全重写索引)之后,一切又正常了。重建索引的一个问题是,它会阻止表上的并发会话。你可能认为你可以同时进行这个操作(这不会阻塞),但是对于系统索引来说,情况并非如此。
postgres=# reindex index concurrently pg_class_oid_index;
ERROR: cannot reindex system catalogs concurrently
所以现在我们知道,当我们已经连接到一个正在运行的系统上时,我们如何能够修复一个损坏的索引。一个更糟糕的情况是这样的
postgres=# select pg_relation_filepath('pg_class_oid_index');
pg_relation_filepath
----------------------
base/12710/16389
(1 row)
postgres=# \! dd if=/dev/zero of=/u02/pgdata/DEV/base/12710/16389 bs=8k count=2
2+0 records in
2+0 records out
16384 bytes (16 kB, 16 KiB) copied, 0.00029232 s, 56.0 MB/s
postgres=# \q
21:57:03 postgres@centos8pg:/home/postgres/ [pgdev] pg_ctl restart -m fast
这和之前的测试是一样的(注意,文件名改变了,因为重建索引创建了一个全新的索引),但是另外PostgreSQL也被重新启动了。到现在为止,一切似乎都很好,因为我们在PostgreSQL启动时没有看到任何问题,除了:
postgres@centos8pg:/home/postgres/ [pgdev] psql postgres
psql: error: could not connect to server: FATAL: index "pg_class_oid_index" contains unexpected zero page at block 0
HINT: Please REINDEX it.
你已经不能连接了,那么你怎么能重建被破坏的索引呢?有一个叫做 "ignore_system_indexes "的参数,你可以用来处理这种情况。它告诉PostgreSQL忽略系统表的任何索引。
postgres@centos8pg:/home/postgres/ [pgdev] echo "ignore_system_indexes='true'" >> $PGDATA/postgresql.auto.conf
postgres@centos8pg:/home/postgres/ [pgdev] pg_ctl restart -m fast
一旦设置好了,就可以再次进行连接,你可以进行同样的修复动作。
ostgres@centos8pg:/home/postgres/ [pgdev] psql -X postgres
psql (13devel)
Type "help" for help.
postgres=# reindex index pg_class_oid_index;
REINDEX
postgres=# alter system set ignore_system_indexes = 'false';
ALTER SYSTEM
postgres=# \q
postgres@centos8pg:/home/postgres/ [pgdev] pg_ctl restart -m fast
...
postgres@centos8pg:/home/postgres/ [pgdev] psql -X postgres
psql (13devel)
Type "help" for help.
postgres=#
希望你在现实生活中永远不会看到系统索引的损坏,但如果你看到了,知道如何修复它是很好的。
小结
ignore_system_indexes是一个开发者选项参数,也就是说在postgresql.conf里面默认是没有的,还有常用的一些选项,在处理故障时,可以多一些技巧(引自中文手册):
•allow_system_table_mods
(boolean
) 允许对系统表结构的修改。它可以被initdb
使用。这个参数只能在服务器启动时设置。•ignore_system_indexes
(boolean
) 读取系统表时忽略系统索引(但是修改系统表时依然同时更新索引)。这在从被破坏的系统索引中恢复数据的时有用。这个参数在会话开始之后不能被更改。•trace_sort
(boolean
) 如果打开,发出在排序操作中的资源使用的相关信息。只有在编译PostgreSQL时定义了TRACE_SORT
宏, 这个参数才可用(不过,当前在默认情况下就定义了TRACE_SORT
)。•ignore_checksum_failure
(boolean
) 只有当data checksums被启用时才有效。在读取过程中检测到一次校验码失败通常会导致PostgreSQL报告一个错误。设置ignore_checksum_failure
为打开会导致系统忽略失败(但是仍然报告一个警告),并且继续执行。这种行为可能导致崩溃、传播或隐藏损坏或者其他严重的问题。但是,它允许你绕过错误并且在块头部仍然健全的情况下从表中检索未损坏的元组。如果头部被损坏,即便这个选项被启用系统也将报告一个错误。默认设置是off
,并且只能被超级用户改变。•zero_damaged_pages
(boolean
) 检测到一个损坏的页面头部通常会导致PostgreSQL报告一个错误,并且中止当前事务。把zero_damaged_pages
设置为打开会让系统报告一个警告、把损坏的页面填充零,然后继续处理。这种行为会毁掉数据,即被损坏页面上的所有行。但是它允许你绕开错误并且从可能存在表中的任何未损坏页面中检索行。如果由于一次硬件或软件错误而发生毁坏,这种方法可用于恢复数据。通常你不应该把它设置为打开,除非你已经彻底放弃从表的损坏页面中恢复数据。被填充零的页面不会被强制到磁盘上,因此我们推荐在再次关闭这个参数之前先重建表或索引。默认的设置是off
,并且只有超级用户可以改变它。
当然,也可以使用单用户登录进行操作,处理年龄也是一样的
$ postgres --single -P -D data template1
PostgreSQL stand-alone backend 9.6.11
backend> reindex system template1;
CTRL-D
$ postgres --single -P -D data template1
PostgreSQL stand-alone backend 9.6.11
backend> $
前文译自 https://blog.dbi-services.com/dealing-with-corrupted-system-indexes-in-postgresql/