Postgresql开发 Page

  • 本文章基于Postgresql 9.5
  • Page数据页结构
  • tablepce、table、toast、index数据文件位置
  • hexpdump解析数据文件



Page结构

PG数据表存储在N个数据文件中,每个数据文件有N个Page(大小默认为8K,可在编译安装时指定)组成,Page为Pg最小存取单元。

普通数据表存储结构

数据页

数据页8Kb = 8 * 1024 Byte, 其中分为五部分:

  • PageHeaderData: 24字节长,包含关于页面的一般信息,包括空闲空间指针。24Byte;
  • ItemIdData: 一个记录(偏移量,长度)对的数组,指向实际项。 4Byte;
  • Free Space: 为分配的空间(空闲空间)
    • 新itemIdData指针从头部开始分配
    • 新数据Items从结尾开始分配
  • Items 实际的项本身 – 实际结构取决于表包含的内容,表和序列都是用HeapTupleHeaderData结构, 索引需要不同对待。
  • Special Space 索引访问模式相关的数据。不同的索引访问方式存放不同的数据。在普通表中为空。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/* include/stotage/bufpage.h
* disk page organization
*
* space management information generic to any page
*
* pd_lsn - identifies xlog record for last change to this page.
* pd_checksum - page checksum, if set.
* pd_flags - flag bits.
* pd_lower - offset to start of free space.
* pd_upper - offset to end of free space.
* pd_special - offset to start of special space.
* pd_pagesize_version - size in bytes and page layout version number.
* pd_prune_xid - oldest XID among potentially prunable tuples on page.
*/
typedef struct
{
uint32 xlogid; /* high bits */ //4Byte
uint32 xrecoff; /* low bits */ //4Byte
} PageXLogRecPtr;


typedef struct PageHeaderData
{
/* XXX LSN is member of *any* block, not only page-organized ones */
PageXLogRecPtr pd_lsn; /* LSN: next byte after last byte of xlog
* record for last change to this page */ // 8Byte
uint16 pd_checksum; /* checksum */ //2Byte
uint16 pd_flags; /* flag bits, see below */ //2Byte
LocationIndex pd_lower; /* offset to start of free space */ //2Byte
LocationIndex pd_upper; /* offset to end of free space */ //2Byte
LocationIndex pd_special; /* offset to start of special space */ //2Byte
uint16 pd_pagesize_version; //2Byte
TransactionId pd_prune_xid; /* oldest prunable XID, or zero if none */ //4Byte
ItemIdData pd_linp[FLEXIBLE_ARRAY_MEMBER]; /* line pointer array */
} PageHeaderData;
  • pg_lsn:记录最后一次对page修改的xlog记录id。
  • pg_checksum:页面的校验和,主要是通过函数pg_checksum_block函数生成的,0也是有效地,参数为PageHeaderData和BLCKSZ(page’s size)。当校验和验证失败,即认为当前页面无效。
  • pg_flags:page的flags,具体值为,可以叠加:

    1
    2
    3
    4
    5
    6
    #define PD_HAS_FREE_LINES   0x0001      /* are there any unused line pointers? */
    #define PD_PAGE_FULL 0x0002 /* not enough free space for new
    * tuple? */
    #define PD_ALL_VISIBLE 0x0004 /* all tuples on page are visible to
    * everyone */
    #define PD_VALID_FLAG_BITS 0x0007 /* OR of all valid pd_flags bits */
  • pg_lower和pg_upper:最后一个项指针的位置和最新的tuple位置。主要进行查找空闲位置,进行插入工作。

  • pg_special:page预留的位置,可以存储索引等信息。
  • pg_pagesize_version:page大小以及当前版本。page大小可以通过configure进行设置。version的意思是

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    /*
    * Page layout version number 0 is for pre-7.3 Postgres releases.
    * Releases 7.3 and 7.4 use 1, denoting a new HeapTupleHeader layout.
    * Release 8.0 uses 2; it changed the HeapTupleHeader layout again.
    * Release 8.1 uses 3; it redefined HeapTupleHeader infomask bits.
    * Release 8.3 uses 4; it changed the HeapTupleHeader layout again, and
    * added the pd_flags field (by stealing some bits from pd_tli),
    * as well as adding the pd_prune_xid field (which enlarges the header).
    *
    * As of Release 9.3, the checksum version must also be considered when
    * handling pages.
    */
  • pg_prune_xid: 一般是最后一次删除或者更新的xid;

  • pg_linp 项指针;

ItemIdData指向指针:

1
2
3
4
5
6
7
8
typedef struct ItemIdData
{
unsigned lp_off:15, /* offset to tuple (from start of page) */ //指向Items;
lp_flags:2, /* state of item pointer, see below */
lp_len:15; /* byte length of tuple */
} ItemIdData;

typedef ItemIdData *ItemId;

表与序列的结构HeapTupleHeaderData

  • Tuple 头部信息
  • 实际数据信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
typedef struct HeapTupleFields
{
TransactionId t_xmin; /* inserting xact ID */
TransactionId t_xmax; /* deleting or locking xact ID */
union
{
CommandId t_cid; /* inserting or deleting command ID, or both */
TransactionId t_xvac; /* old-style VACUUM FULL xact ID */
} t_field3;
} HeapTupleFields;

typedef struct DatumTupleFields
{
int32 datum_len_; /* varlena header (do not touch directly!) */
int32 datum_typmod; /* -1, or identifier of a record type */
Oid datum_typeid; /* composite type OID, or RECORDOID */
/*
* Note: field ordering is chosen with thought that Oid might someday
* widen to 64 bits.
*/
} DatumTupleFields;

typedef struct BlockIdData
{
uint16 bi_hi;
uint16 bi_lo;
} BlockIdData;

typedef struct ItemPointerData
{
BlockIdData ip_blkid;
OffsetNumber ip_posid; // uint16
}

struct HeapTupleHeaderData
{
union
{
HeapTupleFields t_heap;
DatumTupleFields t_datum;
} t_choice;
ItemPointerData t_ctid; /* current TID of this or newer tuple (or a
* speculative insertion token) */
/* Fields below here must match MinimalTupleData! */
uint16 t_infomask2; /* number of attributes + various flags */ //2Byte
uint16 t_infomask; /* various flag bits, see below */ //2Byte
uint8 t_hoff; /* sizeof header incl. bitmap, padding */ //1Byte
/* ^ - 23 bytes - ^ */
bits8 t_bits[FLEXIBLE_ARRAY_MEMBER]; /* bitmap of NULLs */
/* MORE DATA FOLLOWS AT END OF STRUCT */
};

Rel结构体展开

1
2
3
4
5
6
7
8
9
10
Field           Type            Length  Offset  Description
t_xmin TransactionId 4 bytes 0 insert XID stamp
t_xmax TransactionId 4 bytes 4 delete XID stamp
t_cid CommandId 4 bytes 8 insert and/or delete CID stamp (overlays with t_xvac)
t_xvac TransactionId 4 bytes 8 XID for VACUUM operation moving a row version
t_ctid ItemPointerData 6 bytes 12 current TID of this or newer row version
t_infomask2 uint16 2 bytes 18 number of attributes, plus various flag bits
t_infomask uint16 2 bytes 20 various flag bits
t_hoff uint8 1 byte 22 offset to user data
//注意:t_cid和t_xvac为联合体,共用存储空间
  • xmin 和 xmax 是插入,删除,更新操作时的事务ID,插入时会在xmin内写入当前事务ID,删除时在xmax写入当前事务ID,更新是进行删除后再插入。
  • t_cid 是指一个事务内的命令ID, 每个事务都从0开始。
  • t_ctid 物理ID。
  • t_infomask2 取值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    /*
    * information stored in t_infomask2:
    */
    #define HEAP_NATTS_MASK 0x07FF /* 11 bits for number of attributes */
    /* bits 0x1800 are available */
    #define HEAP_KEYS_UPDATED 0x2000 /* tuple was updated and key cols
    * modified, or tuple deleted */
    #define HEAP_HOT_UPDATED 0x4000 /* tuple was HOT-updated */
    #define HEAP_ONLY_TUPLE 0x8000 /* this is heap-only tuple */

    #define HEAP2_XACT_MASK 0xE000 /* visibility-related bits */
  • t_infomask 取值:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    /*
    * information stored in t_infomask:
    */
    #define HEAP_HASNULL 0x0001 /* has null attribute(s) */
    #define HEAP_HASVARWIDTH 0x0002 /* has variable-width attribute(s) */
    #define HEAP_HASEXTERNAL 0x0004 /* has external stored attribute(s) */
    #define HEAP_HASOID 0x0008 /* has an object-id field */
    #define HEAP_XMAX_KEYSHR_LOCK 0x0010 /* xmax is a key-shared locker */
    #define HEAP_COMBOCID 0x0020 /* t_cid is a combo cid */
    #define HEAP_XMAX_EXCL_LOCK 0x0040 /* xmax is exclusive locker */
    #define HEAP_XMAX_LOCK_ONLY 0x0080 /* xmax, if valid, is only a locker */

    /* xmax is a shared locker */
    #define HEAP_XMAX_SHR_LOCK (HEAP_XMAX_EXCL_LOCK | HEAP_XMAX_KEYSHR_LOCK)

    #define HEAP_LOCK_MASK (HEAP_XMAX_SHR_LOCK | HEAP_XMAX_EXCL_LOCK | \
    HEAP_XMAX_KEYSHR_LOCK)
    #define HEAP_XMIN_COMMITTED 0x0100 /* t_xmin committed */
    #define HEAP_XMIN_INVALID 0x0200 /* t_xmin invalid/aborted */
    #define HEAP_XMIN_FROZEN (HEAP_XMIN_COMMITTED|HEAP_XMIN_INVALID)
    #define HEAP_XMAX_COMMITTED 0x0400 /* t_xmax committed */
    #define HEAP_XMAX_INVALID 0x0800 /* t_xmax invalid/aborted */
    #define HEAP_XMAX_IS_MULTI 0x1000 /* t_xmax is a MultiXactId */
    #define HEAP_UPDATED 0x2000 /* this is UPDATEd version of row */
    #define HEAP_MOVED_OFF 0x4000 /* moved to another place by pre-9.0
    * VACUUM FULL; kept for binary
    * upgrade support */
    #define HEAP_MOVED_IN 0x8000 /* moved from another place by pre-9.0
    * VACUUM FULL; kept for binary
    * upgrade support */
    #define HEAP_MOVED (HEAP_MOVED_OFF | HEAP_MOVED_IN)

    #define HEAP_XACT_MASK 0xFFF0 /* visibility-related bits */
  • t_hoff: HeapTupleHeaderData长度,如果有OID会增加4,受字节对齐影响,会增加8

  • t_bits: 指向实际具体数据。


tablespace 位置

查看数据文件首先知道数据文件存放于表空间下,首先需要知道表空间OId

1
2
3
4
5
6
7
Postgres=# select oid, datname from pg_database ;
oid | datname
-------+-----------
1 | template1
13356 | template0
13361 | highgo
16384 | Postgres

其次表空间分为系统表空间与用户自定义表空间。

PG系统表空间默认有两个:pg_default 和 pg_global

pg_default 的文件存放在PGDATA/base目录下

pg_global 的文件存放在PGDATA/global目录下。

用户自定义的表空间在PGDATA/pg_tblspc存在一个软连接,指向实际的存放目录 create tablespace 指定的location 软连接以表空间OID命名。

1
2
3
4
5
6
7
Postgres=# create tablespace test owner Postgres LOCATION '/home/Postgres/db';
Postgres=# select oid, * from pg_tablespace ;
oid | spcname | spcowner | spcacl | spcoptions
-------+------------+----------+--------+------------
1663 | pg_default | 10 | |
1664 | pg_global | 10 | |
16389 | test | 10 | |
1
2
shell$ /home/Postgres/dbtest/pg_tblspc
0 lrwxrwxrwx. 1 Postgres Postgres 15 Oct 7 10:28 16389 -> /home/Postgres/db


table 位置

pg中每个表都是一个文件,文件以filenode命名。filenode的值可以从pg_classrelfilenode字段获取。
当表大小超过1G的时候,表文件会被分割,第一个文件的名字是filenode,第二个则是filenode.1 依次后推,
表文件大小限额1G是PG默认值,该值可以通过编译–with-segsize来改变。

方法一

1
2
3
4
Postgres=# select relname, relfilenode,  relnamespace from pg_class where relname = 'pagetest';
relname | relfilenode | relnamespace
----------+-------------+--------------
pagetest | 16406 | 2200

方法二

1
2
3
Postgres=# select pg_relation_filepath('pagetest');
-[ RECORD 1 ]--------+-----------------
pg_relation_filepath | base/16384/16406

在表空间目录下, 通常与表Filenode相关的还有两个文件, filenode_fsm 和 filenode_vm .
fsm 文件为 free space map. 用于跟踪表文件中的空闲空间。
vm 文件为 visibility map. 用于跟踪哪些page包含对所有事务都可见的tuple。


index 位置

在Pagetest表上创建索引

1
Postgres=# create index ON pagetest using brin ( id );

查看索引存放文件位置:

1
2
3
4
Postgres=# select pg_relation_filepath('pagetest_id_idx');
pg_relation_filepath
----------------------
base/16384/16409


Pageinspect查看Page内容

安装

1
2
3
cd $PGSRC/contrib/pageinspect
make
make install

简单使用

1
create extension pageinspect ;


Table

查看raw_page 十六进制内容:

1
2
3
\x     # Expanded display is on.

select * from get_raw_page('pagetest', 0);

header & items;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Postgres=# select * from page_header(get_raw_page('pagetest',0));  
lsn | checksum | flags | lower | upper | special | pagesize | version | prune_xid
-----------+----------+-------+-------+-------+---------+----------+---------+-----------
0/185E030 | 0 | 0 | 40 | 8032 | 8192 | 8192 | 4 | 0
(1 row)


Postgres=# select * from heap_page_items(get_raw_page('pagetest',0));
lp | lp_off | lp_flags | lp_len | t_xmin | t_xmax | t_field3 | t_ctid | t_infomask2 | t_infomask | t_hoff | t_bits | t_oid
----+--------+----------+--------+--------+--------+----------+--------+-------------+------------+--------+--------+-------
1 | 8152 | 1 | 39 | 1842 | 0 | 0 | (0,1) | 3 | 2306 | 24 | |
2 | 8112 | 1 | 39 | 1843 | 0 | 0 | (0,2) | 3 | 2306 | 24 | |
3 | 8072 | 1 | 39 | 1844 | 0 | 0 | (0,3) | 3 | 2306 | 24 | |
4 | 8032 | 1 | 39 | 1845 | 0 | 0 | (0,4) | 3 | 2306 | 24 | |
(4 rows)

update: page_items 变化

1
2
3
4
5
6
7
8
Postgres=# select * from heap_page_items(get_raw_page('pagetest', 0));
lp | lp_off | lp_flags | lp_len | t_xmin | t_xmax | t_field3 | t_ctid | t_infomask2 | t_infomask | t_hoff | t_bits | t_oid
----+--------+----------+--------+--------+--------+----------+--------+-------------+------------+--------+--------+-------
1 | 8152 | 1 | 39 | 1842 | 0 | 0 | (0,1) | 3 | 2306 | 24 | |
2 | 8112 | 1 | 39 | 1843 | 0 | 0 | (0,2) | 3 | 2306 | 24 | |
3 | 8072 | 1 | 39 | 1844 | 0 | 0 | (0,3) | 3 | 2306 | 24 | |
4 | 8032 | 1 | 39 | 1845 | 1846 | 0 | (0,5) | 16387 | 258 | 24 | |
5 | 7992 | 1 | 39 | 1846 | 0 | 0 | (0,5) | 32771 | 10242 | 24 | |

第4条记录 t_xmax 被修改为 1846 , t_xmax 只有被delete的时候才会被标记, 而第五条记录发现t_xmin被修改为1846,说明是同一事务进行操作,说明进行的操作为update;

insert page_header变化

1
2
3
4
5
Postgres=# select * from page_header(get_raw_page('pagetest',0));
lsn | checksum | flags | lower | upper | special | pagesize | version | prune_xid
-----------+----------+-------+-------+-------+---------+----------+---------+-----------
0/18603F0 | 0 | 0 | 48 | 7952 | 8192 | 8192 | 4 | 1846
(1 row)

进行insert操作insert into pagetest values ( 7, '7', 'g' );

1
2
3
    lsn    | checksum | flags | lower | upper | special | pagesize | version | prune_xid 
-----------+----------+-------+-------+-------+---------+----------+---------+-----------
0/1860460 | 0 | 0 | 52 | 7912 | 8192 | 8192 | 4 | 1846

比较发现 lsn, lower, upper 三项发生了变化。
lsn 记录最后一次对page修改的xlog记录id
lower 每次从前向后申请 4Byte字节
upper 每次从后向前从Free Space申请39字节,因为字节对齐 申请40Byte;


index

查看索引类型:

1
2
3
4
5
6
7
8
9
Postgres=# \d pagetest
Table "public.pagetest"
Column | Type | Modifiers
--------+-----------------------+-----------
id | integer |
c1 | character(8) |
c2 | character varying(16) |
Indexes:
"pagetest_id_idx" brin (id)

查看Page Header;

1
2
3
4
Postgres=# SELECT * from  page_header(get_raw_page('pagetest_id_idx', 0));
lsn | checksum | flags | lower | upper | special | pagesize | version | prune_xid
-----------+----------+-------+-------+-------+---------+----------+---------+-----------
0/1872A90 | 0 | 0 | 24 | 8184 | 8184 | 8192 | 4 | 0

根据pageinspect提供相关索引进行查看信息:
不同类型索引使用的存储结构不同; 常见索引如下:

  • brin
  • btree
  • gin
  • gist
  • hash
  • spgist


hexdump 解析数据文件

可以对照pageinspesct 进行信息对照。

1
2
3
shell$ hexdump -C 16406 -s 0 -n 8
00000000 00 00 00 00 / 30 e0 85 01 |....0...|
00000008
  • 前4Byte是TimeLineID 0x00000000
  • 后4Byte是 0x0185e030

组合 0/185e030

ItemIdData:

1
2
3
4
5
6
7
8
9
Postgres$ hexdump -C 16406 -s 24 -n 4
00000018 d8 9f 4e 00 |..N.|
0000001c

Postgres$ echo $(( 0x9fd8 & ~(1 << 15) )) ## 取低15位,指向Items;-实际项;
8152

Postgres$ echo $(( 0x004e >> 1 )) ## 取高15位; 得到lp_len (Tuple元组 length)
39

第一个Tuple偏移8152Byte;

1
hexdump -C 16406 -s 8152 -n 39

itemidData->lp_len = 39;

t_hoff = 24; ==> hexdump -C 16406 -s 8152+22 -n 1

第一条Tuple元组信息 size = 39 - 24 = 15;

1
2
3
4
5
6
7
8
9
10
Postgres$ hexdump -C 16406 -s 8152+t_hoff -n (itemidData->lp_len - t_hoff)
01 00 00 00 13 31 20 20 20 20 20 20 20 05 61 |.....1 .a|

回顾我们的表结构:
create table pagetest (id int,c1 char(8),c2 varchar(16));
第1个字段为int,第2个字段为定长字符,第3个字段为变长字符。
相应的数据:
id=\x00000001,数字1
c1=\x133120202020202020,字符串,无需高低位变换,第1个字节\x13为标志位,后面是字符'1'+7个空格 ascii->hex;
c2=\x0561,字符串,第1个字节\x05为标志位,后面是字符'a'

那么第二条,则是, 因为实际数据申请,从文件向前申请。

1
2
3
4
hexdump -C 16406 -s 8152+t_hoff-itemidData->lp_len -n 15
==>
hexdump -C 16406 -s 8137 -n 15
00 00 00 13 32 20 20 20 20 20 20 20 05 62 00 |....2 .b.|

补充:

Tuple中变长字段尤其需要注意,因为其保存大尺寸数据,很容易超过Page大小,Postgresql不允许一行数据跨Page存储。因此引入TOAST机制来处理。采用压缩 + 切片的方法将大数据分割成小数据,变成多行保存在对用的toast表中,并对它们进行索引。所以从文件角度来看,一行大数据可能被分割存在于多个数据文件中。(普通表与toast表分别对应的文件)。

查看Pagetest普通表与它的toast表:

1
2
3
4
Postgres=# select oid, relname, relnamespace, relfilenode, reltablespace, relpages, reltuples, reltoastrelid, relkind from pg_class where relname = 'pagetest';
oid | relname | relnamespace | relfilenode | reltablespace | relpages | reltuples | reltoastrelid | relkind
-------+----------+--------------+-------------+---------------+----------+-----------+---------------+---------
16406 | pagetest | 2200 | 16406 | 0 | 1 | 6 | 0 | r

所有的toast表都在模式(schema)pg_toast下,查看toast表结构和数据:

1
2
3
4
5
6
7
8
9
\d+  pg_toast.pg_toast_2606
TOAST table "pg_toast.pg_toast_2606"
Column | Type | Storage
------------+---------+---------
chunk_id | oid | plain
chunk_seq | integer | plain
chunk_data | bytea | plain

Invalid command \. Try \? for help.
  • chunk_id 具有同样的chunk_id值得所有行组成原表的toast字段的一行数据。
  • chunk_seq 表示该行切片数据在完整数据中的顺序。
  • chunk_data 实际存储的切片数据。

一张普通的表最多对应一张toast表,但是每个字段根据不同的数据类型具有不同的toast策略。修改toast策略,不会影响已有的数据存放方式。

  • plain:不压缩,不切片。不会触发toast机制。
  • extended:优先压缩,后切片。
  • external:不压缩,只切片。用空间换时间的策略,提高操作效率。
  • main:尽量不切片。
1
2
3
4
5
6
7
8
9
10
Postgres=# \d+ pagetest
Table "public.pagetest"
Column | Type | Modifiers | Storage | Stats target | Description
--------+-----------------------+-----------+----------+--------------+-------------
id | integer | | plain | |
c1 | character(8) | | extended | |
c2 | character varying(16) | | extended | |
Indexes:
"pagetest_id_idx" brin (id)
Replica Identity: FULL

当将要保存到表的一行数据超过一个阈值(BLOCK/4 ~ 2KB)时,会触发toast机制,如果产生切片则保存到对应的toast表中。


Pageinit
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// include/pg_config.h
#define BLCKSZ 8192


void
PageInit(Page page, Size pageSize, Size specialSize) //rocky_pageinit;
{
PageHeader p = (PageHeader) page;

specialSize = MAXALIGN(specialSize);

Assert(pageSize == BLCKSZ);
Assert(pageSize > specialSize + SizeOfPageHeaderData);

/*
* Make sure all fields of page are zero, as well as unused space
*/
MemSet(p, 0, pageSize);

p->pd_lower = SizeOfPageHeaderData;
p->pd_upper = pageSize - specialSize;
p->pd_special = pageSize - specialSize;
PageSetPageSizeAndVersion(page, pageSize, PG_PAGE_LAYOUT_VERSION);
}


数据更新

数据更新的最终结果就是把变更的内容持久化到硬盘上。一次事务可能涉及多个page的改变。如果直接刷盘会产生多次随机写,付出的代价相对较高。所以在涉及层面采用写缓冲,异步和WAL来解决性能上的问题,并且WAL机制也保证了事务的持久性和数据的完整性,还使得在线备份和时间回复(PITR)称为可能。


事务过程

存储引擎在执行更新类操作时,首先开启事务,然后将变更后的内容记入Wal buffer, 并更新shared buffer中的page,最后提交事务,相应客户端。
shared buffer中的page发生更新后被标记为dirty page,之后由其他进程完成磁盘回写。
事务提交可以设置是否等待WAL记录被写入磁盘。控制参数为synchronous_commit。
设置off时对性能有一定提升,程序将不会等待本次WAL记录写入到磁盘,而直接影响客户端。


WAL写入磁盘

默认情况下,WAL是在事务提交时写入磁盘。以保证提交的事务不会因为宕机而丢失。当synchronous_commit设置为off时,WAL写入磁盘的过程由Wal write进程异步完成,这种情况下宕机存在丢失最近提交的事务风险。但不会影响数据库的一致性。


Dirty Page回写磁盘

一般情况下,每隔一段时间writer进程就会扫描shared buffer, 将Dirty Page刷入操作系统的Page Cache,并记录处理过的Dirty Page,之后由系统内核处理page cache刷入磁盘,当空闲内存低于阈值 或 脏页驻留时间超过阈值时,触发回写磁盘。

另一种情况是由checkpointer进程定义执行checkpoint,强制当前处理过的dirty page回写磁盘,保证数据持久化,为事务前滚提供参照点。默认情况下(full_page_write = on), checkpoint发生后,在page第一次写的时候会将整个page写入WAL,以保证宕机或者备份拷贝时,产生的partial wirte page可以保证被正确的完整的page覆盖来修复。


空间回收

在PostgreSQL中,delete和一些的update(例如,涉及到索引字段的更新)会使表和索引中的旧行(tuple)被废弃掉。这些废弃的tuple称之为dead tuple,他们仍然占用空间,称为一种资源浪费。

PostgreSQL的VACUUM机制就是用来回收这些dead tuple空间的,用来回收利用。

  • Full Vacuum

    1
    2
    3
    VACUUM FULL VERBOSE;
    VACUUM FULL t_user_info;
    ANALYZE;
  • Lazy Vacuum

    1
    2
    VACUUM t_user_info;
    VACUUM ANALYZE t_user_info;


补充

Postgresql 10 数据库文件布局

欣赏此文? 求鼓励,求支持!