PostgreSQL single-user mode usage,like Repair Database

4 minute read

背景

PostgreSQL目前的xid还是32位的,所以每约20亿事务需要freeze,将tuple的事务状态改成freeze xid,如果你没有做,那么数据库会先提示你防止wrap,然后如果在消耗殆尽前100万个事务时,强制关闭数据库。此时就需要进入单用户才能操作。

单用户模式如何进入

PostgreSQL单用户模式通过以下语法来调用,需要停库,使用postgres命令加单用户选项启动为单用户模式:

postgres --single -D $PGDATA db_name  

可选选项:

-E   
echo all commands  
-j   
Disables use of newline as a statement delimiter.  
-r $FILENAME  
Send all server log output to filename. In normal multiuser mode, this option is ignored, and stderr is used by all processes.  

单用户模式典型的应用,1.当多用户模式不接收所有命令时, 2.initdb时 3.修复系统表。

例如:

数据库中任何带relfrozenxid标记的记录,年龄不能超过2^31(二十亿);当数据库中存在年龄大于{(2^31)-1千万}的记录时,数据库将报类似如下提示:

WARNING:  database "mydb" must be vacuumed within 177009986 transactions  
HINT:  To avoid a database shutdown, execute a database-wide VACUUM in "mydb".  

提示需要手工的消除这些警告,如果忽略不去处理的话,后面数据库可能需要关闭来处理,下面会提到.处理的方法是使用超级用户

VACUUM mydb.  

(这里用到超级用户的原因是需要更新系统表的datfrozenxid列值)

如果忽略上面的警告,当数据库中存在年龄大于{(2^31)-1百万}的记录时,数据库将报类似如下错误:

ERROR:  database is not accepting commands to avoid wraparound data loss in database "mydb"  
HINT:  Stop the postmaster and use a standalone backend to VACUUM in "mydb".  

数据库将不再新建任何新的事务,只能通过单用户模式修复。

(100万被认为是一个比较安全的临界值,管理员可以在此年龄范围内对数据库进行VACUUM修复,)

单用户使用举例

# su - postgres  
(确保数据库已经关闭)  
$ postgres --single mydb_name  
  
postgres@db-bak-192-168-105-35-> postgres --single rmt_rescue  
PostgreSQL stand-alone backend 9.0.1  
backend> vacuum full; (不带任何参数的VACUUM将对所有当前用户有权限的表进行操作)  
backend> Ctrl+D 退出单用户模式.  

通常可以进入单用户模式后,查出最大年龄的表,对表进行vacuum freeze.

backend> select nspname, relname, age(relfrozenxid) from pg_class t1 , pg_namespace t2 where t1.relnamespace=t2.oid and relkind in ('r','t','m') order by age(relfrozenxid) desc limit 10;  
         1: nspname     (typeid = 19, len = 64, typmod = -1, byval = f)  
         2: relname     (typeid = 19, len = 64, typmod = -1, byval = f)  
         3: age (typeid = 23, len = 4, typmod = -1, byval = t)  
        ----  
         1: nspname = "pg_catalog"      (typeid = 19, len = 64, typmod = -1, byval = f)  
         2: relname = "pg_statistic"    (typeid = 19, len = 64, typmod = -1, byval = f)  
         3: age = "731601622"   (typeid = 23, len = 4, typmod = -1, byval = t)  
        ----  
         1: nspname = "public"  (typeid = 19, len = 64, typmod = -1, byval = f)  
         2: relname = "tbl_pos_28"      (typeid = 19, len = 64, typmod = -1, byval = f)  
         3: age = "596839371"   (typeid = 23, len = 4, typmod = -1, byval = t)  
        ----  
         1: nspname = "public"  (typeid = 19, len = 64, typmod = -1, byval = f)  
         2: relname = "tbl_pos_2"       (typeid = 19, len = 64, typmod = -1, byval = f)  
         3: age = "592403555"   (typeid = 23, len = 4, typmod = -1, byval = t)  
        ----  
         1: nspname = "public"  (typeid = 19, len = 64, typmod = -1, byval = f)  
         2: relname = "tbl_pos" (typeid = 19, len = 64, typmod = -1, byval = f)  
         3: age = "587929977"   (typeid = 23, len = 4, typmod = -1, byval = t)  
        ----  
         1: nspname = "public"  (typeid = 19, len = 64, typmod = -1, byval = f)  
         2: relname = "tbl_pos_1"       (typeid = 19, len = 64, typmod = -1, byval = f)  
         3: age = "587927563"   (typeid = 23, len = 4, typmod = -1, byval = t)  
        ----  
         1: nspname = "public"  (typeid = 19, len = 64, typmod = -1, byval = f)  
         2: relname = "tbl_pos_5"       (typeid = 19, len = 64, typmod = -1, byval = f)  
         3: age = "583493275"   (typeid = 23, len = 4, typmod = -1, byval = t)  
        ----  
         1: nspname = "public"  (typeid = 19, len = 64, typmod = -1, byval = f)  
         2: relname = "tbl_pos_24"      (typeid = 19, len = 64, typmod = -1, byval = f)  
         3: age = "582569633"   (typeid = 23, len = 4, typmod = -1, byval = t)  
        ----  
         1: nspname = "public"  (typeid = 19, len = 64, typmod = -1, byval = f)  
         2: relname = "tbl_pos_7"       (typeid = 19, len = 64, typmod = -1, byval = f)  
         3: age = "579071765"   (typeid = 23, len = 4, typmod = -1, byval = t)  
        ----  
         1: nspname = "public"  (typeid = 19, len = 64, typmod = -1, byval = f)  
         2: relname = "tbl1"    (typeid = 19, len = 64, typmod = -1, byval = f)  
         3: age = "577666039"   (typeid = 23, len = 4, typmod = -1, byval = t)  
        ----  
         1: nspname = "public"  (typeid = 19, len = 64, typmod = -1, byval = f)  
         2: relname = "tbl_pos_16"      (typeid = 19, len = 64, typmod = -1, byval = f)  
         3: age = "577658079"   (typeid = 23, len = 4, typmod = -1, byval = t)  
        ----  

将年龄降为1

backend> set vacuum_freeze_min_age =0;  
backend> vacuum freeze pg_catalog.pg_statistic;  

降全库年龄

backend> set vacuum_freeze_min_age =0;  
backend> vacuum freeze;  

附录

1. 查看表的年龄:

rmt_rescue=> SELECT relname, age(relfrozenxid) FROM pg_class WHERE relname='tbl_test1';  
  relname  | age   
-----------+-----  
 tbl_test1 |  14  
(1 row)  

2. 查看数据库的年龄:

rmt_rescue=> select datname,age(datfrozenxid) from pg_database;  
  datname   |  age     
------------+--------  
 template1  |   8099  
 template0  | 676734  
 postgres   | 676734  
 rmt_rescue | 676734  
(4 rows)  

3. 查看表中记录的年龄:

rmt_rescue=> select id,ctid,cmin,cmax,xmin,xmax,age(xmin),age(xmax) from tbl_test where id=1;  
 id | ctid  | cmin | cmax |  xmin  | xmax | age |    age       
----+-------+------+------+--------+------+-----+------------  
  1 | (0,1) |    0 |    0 | 677333 |    0 |  53 | 2147483647  
(1 row)  
  
年龄=53  
  
rmt_rescue=> update tbl_test set name='test' where id=1;  
UPDATE 1  
rmt_rescue=> select id,ctid,cmin,cmax,xmin,xmax,age(xmin),age(xmax) from tbl_test where id=1;  
 id |    ctid    | cmin | cmax |  xmin  | xmax | age |    age       
----+------------+------+------+--------+------+-----+------------  
  1 | (4424,177) |    0 |    0 | 677387 |    0 |   1 | 2147483647  
(1 row)  
  
更新该记录后,年龄=1.  

4. 年龄:

normal xid和当前事务id进行比较得到的一个值.SELECT语句也会增加数据库当前事务ID的值.

还有一种比较特殊的XID不参与比较,在任何状态下都被视为”in the past”状态,这就是frozenxid,使用vacuum freezen或vacuum且

当年龄大于vacuum_freeze_min_age时可以将tuple的XID转换为in the past状态,不过update后XID又会回到NORMAL状态.因此建议将

vacuum_freeze_min_age设置为: tuple从创建到不会被更新所经过的年龄是比较合适的.

5. age函数

					    List of functions  
   Schema   | Name | Result data type |                   Argument data types                    |  Type    
------------+------+------------------+----------------------------------------------------------+--------  
 pg_catalog | age  | interval         | timestamp with time zone                                 | normal  
 pg_catalog | age  | interval         | timestamp with time zone, timestamp with time zone       | normal  
 pg_catalog | age  | interval         | timestamp without time zone                              | normal  
 pg_catalog | age  | interval         | timestamp without time zone, timestamp without time zone | normal  
 pg_catalog | age  | integer          | xid                                                      | normal  
(5 rows)  

Flag Counter

digoal’s 大量PostgreSQL文章入口