Postgres数据库采用MVCC(多版本并发控制)机制来保证数据的完整性,与Mysql和Oracle数据库采用undo/回滚段来保存旧数据不同,pg中没有undo这一概念,pg的多版本并发是通过在表中数据行的多个版本来实现的。也就是说,在删除和更新旧数据的时候,pg并不是直接删除或者修改老数据,而是对老数据加上标识或者在插入一条新数据后再对老数据加上标识。为了保证数据库的完整性,pg官方引入了WAL日志的概念。

预写式日志(Write Ahead Log,WAL)的核心概念是对数据文件(存储着表和索引)的这些动作必须在被记录到日志后才写入到数据文件。简单来说就是数据在写入磁盘之前,要先记录在wal日志中。这样做有以下几个好处:

  1. 显著减少写磁盘的次数:不需要在每个事物提交时都写入数据到磁盘,大大减少了系统的I/O次数。
  2. 保证数据的持久性:在系统异常崩溃时,利用WAL日志可以恢复到数据库崩溃之前最近一次的正常状态。在Postgres8.0以后的版本,我们还可以利用基础备份文件和WAL日志文件恢复到任意时间节点。
  3. 支持高并发:WAL日志支持多个并发事物的写入操作,从而提高了系统的并发性能。
  4. 可以实现数据库的备份和恢复:通过备份WAL日志,可以实现pg数据库的备份和恢复,保证了数据库的安全性。

当然,使用WAL日志也有不足之处,如下所述:

  1. 存储空间占用:系统产生的wal日志会占据磁盘大量空间,如不及时清理,可能会对系统性能造成影响。
  2. 增加系统复杂度:wal日志需要对事物和日志进行管理,增加了系统的复杂度和开发难度。
  3. 系统性能下降:由于需要对日志进行追加和读取,所以可能会造成一定的写入和读取性能下降,特别是在高并发场景下,可能会增加锁竞争的情况。

WAL日志的大小一般默认为16MB,单个日志写满后会继续写下一个日志,直到磁盘剩余空间不足时才会回收旧的wal日志以便继续使用,可以配置max_wal_size和wal_keep_segments参数来指定允许wal日志保存的最大容量和最小数量。

WAL机制的工作流程:

当数据库中数据发生变更时,先要将变更后内容计入wal buffer中,再将变更后的数据写入data buffer; commit发生时,wal buffer中数据会刷新到磁盘; checkpoint发生时,所有data buffer会被刷新到磁盘。

触发checkpoint的条件:

  • 手动执行checkpoint命令。
  • 执行需要检查点的命令,如pg_ctl stop|restart,pg_start_backup等等。
  • 达到检查点配置时间(checkpoint_timeout,一般默认为5分钟)。
  • max_wal_size已满。

下面我们在Ubuntu系统上看一下wal日志的具体信息。

wal日志文件一般存放在数据目录下的pg_wal或者pg_xlog文件夹下(PostgreSQL从10版本开始,将所用xlog相关的全部用wal替换了)。本机的popstgres数据目录为/var/postgresql/data/。

postgres@SZX5APLN622241:/var/postgresql/data$ ls -lrt
total 456
-rw------- 1 postgres postgres      3 Apr  8  2020 PG_VERSION
drwx------ 2 postgres postgres   4096 Apr  8  2020 pg_twophase
drwx------ 2 postgres postgres   4096 Apr  8  2020 pg_serial
drwx------ 4 postgres postgres   4096 Apr  8  2020 pg_multixact
drwx------ 2 postgres postgres   4096 Apr  8  2020 pg_dynshmem
drwx------ 2 postgres postgres   4096 Apr  8  2020 pg_commit_ts
-rw------- 1 postgres postgres   1636 Apr  8  2020 pg_ident.conf
drwx------ 2 postgres postgres   4096 Apr  8  2020 pg_xact
drwx------ 2 postgres postgres   4096 Apr  8  2020 pg_subtrans
-rw-r--r-- 1 postgres postgres     84 Apr 10  2020 user_info.txt
-rw------- 1 postgres postgres     84 Apr 10  2020 user_info
-rw------- 1 postgres postgres    211 Apr 14  2020 backup_label.old
-rwxr-xr-x 1 postgres postgres  24007 Apr 30  2020 postgresql_bak.conf
-rw------- 1 postgres postgres  16246 Apr 30  2020 logfile
drwx------ 2 postgres postgres   4096 Nov  6  2020 pg_tblspc
drwx------ 2 postgres postgres   4096 Dec 22  2020 pg_replslot
drwx------ 2 postgres postgres   4096 Aug 23  2021 pg_snapshots
-rw------- 1 postgres postgres   4646 Aug 21  2022 pg_hba.conf
-rw------- 1 postgres postgres    210 Nov 15 10:32 postgresql.auto.conf
drwxrwxr-x 3 postgres postgres   4096 Dec 27 12:12 pg_orphaned
-rwxr-xr-x 1 postgres postgres  24044 Mar  7 11:33 postgresql.conf
-rw------- 1 postgres postgres     58 May 13 16:06 postmaster.opts
drwx------ 2 postgres postgres   4096 May 13 16:06 pg_notify
drwx------ 2 postgres postgres   4096 May 13 16:20 global
drwx------ 8 postgres postgres   4096 May 13 16:20 base
drwx------ 4 postgres postgres   4096 May 13 16:29 pg_logical
drwx------ 3 postgres postgres 290816 May 13 16:29 pg_wal
drwx------ 2 postgres postgres   4096 May 13 16:29 pg_stat_tmp
drwx------ 2 postgres postgres   4096 May 13 16:29 pg_stat

本机安装的是PG11.7版本,它的wal日志文件存放在pg_wal目录下。接着看下pg_wal目录下放了哪些文件:

postgres@SZX5APLN622241:/var/postgresql/data/pg_wal$ ls -lrt|head
total 934188
drwx------ 2 postgres postgres   303104 May 24  2022 archive_status
-rw------- 1 postgres postgres 16777216 Mar  7 14:32 000000010000001A0000002B
-rw------- 1 postgres postgres 16777216 Mar  7 14:32 000000010000001A00000025
-rw------- 1 postgres postgres 16777216 Mar  7 14:32 000000010000001A0000002C
-rw------- 1 postgres postgres 16777216 Mar  7 14:32 000000010000001A00000021
-rw------- 1 postgres postgres 16777216 Mar  7 14:33 000000010000001A00000020
-rw------- 1 postgres postgres 16777216 Mar  7 14:33 000000010000001A00000029
-rw------- 1 postgres postgres 16777216 Mar  7 14:33 000000010000001A00000026
-rw------- 1 postgres postgres 16777216 Mar  7 14:33 000000010000001A00000023

从PG 10开始,我们可以用pg_waldump命令来解析wal日志。

postgres@SZX5APLN622241:/var/postgresql/data/pg_wal$ pg_waldump 000000010000001A0000001E
rmgr: XLOG        len (rec/tot):    106/   106, tx:          0, lsn: 1A/1E000028, prev 1A/1D000B38, desc: CHECKPOINT_SHUTDOWN redo 1A/1E000028; tli 1; prev tli 1; fpw true; xid 0:13006; oid 149474; multi 1; offset 0; oldest xid 561 in DB 12409; oldest multi 1 in DB 16385; oldest/newest commit timestamp xid: 0/0; oldest running xid 0; shutdown
pg_waldump: FATAL:  error in WAL record at 1A/1E000028: invalid record length at 1A/1E000098: wanted 24, got 0

另外,从PG 10开始使用pg_switch_wal()来切换归档日志,切换归档日志是指强制服务器切换到一个新的WAL日志文件。PG也提供了相应的函数根据LSN(login Sequence Number日志序列号)获取日志文件名:

postgres@SZX5APLN622241:/var/postgresql/data/pg_wal$ psql
psql (12.18 (Ubuntu 12.18-0ubuntu0.20.04.1), server 11.7)
Type "help" for help.

postgres=# select pg_switch_wal();
 pg_switch_wal 
---------------
 1A/1E0000B0
(1 row)

postgres=# select pg_walfile_name(pg_current_wal_lsn());
     pg_walfile_name      
--------------------------
 000000010000001A0000001F
(1 row)