PostgreSQL笔记22: WAL日志与崩溃恢复原理

纲要

本文深入剖析 PostgreSQL 的 WAL(Write-Ahead Logging)机制及其崩溃恢复原理,涵盖以下核心概念与流程:

  • WAL (Write-Ahead Logging) 预写日志
    • 核心原理:日志先行、顺序写入
    • 性能优势:将随机写转化为顺序写
    • 事务提交与日志落盘
  • Checkpoint 检查点
    • 定义与作用:脏页刷盘、日志回收
    • 触发机制:checkpoint_timeoutmax_wal_size
  • 控制文件 (Control File)
    • pg_control 的结构与作用
    • Latest checkpoint's REDO location 与崩溃恢复起点
  • LSN (Log Sequence Number) 日志序列号
    • 定义与格式:pg_lsn 数据类型
    • 在恢复流程中的作用
  • CLOG (Commit Log) 事务状态日志
    • 事务状态:IN_PROGRESSCOMMITTEDABORTED
    • 存储位置:pg_xact 目录
  • Timeline 时间线
    • 概念与作用:PITR 与流复制中的分支
  • 崩溃恢复流程
    • 数据库启动时的自动恢复
    • REDO 操作从 LSN 点位开始回放
  • 相关工具与命令
    • pg_controldata:查看控制文件信息
    • pg_waldump / pg_walinspect:解析 WAL 日志内容
    • pg_control_checkpoint():查询检查点信息

数据库的故障场景与可靠性诉求

PostgreSQL 作为企业级关系型数据库,在生产环境中面临着错综复杂的运行风险。这些风险可归纳为以下几个层面:

  • 数据库进程自身:代码缺陷(Bug)或断言失败(Assertion Failure)导致进程异常终止。
  • 操作系统层面:内存溢出(OOM Killer)强制终止进程、内核崩溃(Kernel Panic)。
  • 硬件层面:磁盘坏块、RAID 卡故障、电源中断等物理失效。

面对上述不可控因素,数据库系统必须具备一套完备的故障恢复机制,以确保在崩溃重启后,已提交事务的数据不丢失(持久性,Durability),且数据库整体处于一致性的状态。

WAL 预写日志机制

核心原理

WAL(Write-Ahead Logging)即预写日志,是保障数据库数据完整性的标准方法。其中心思想是:对数据文件(表和索引)的变更,必须在描述这些变更的日志记录被刷新到持久化存储之后才能写入

在 PostgreSQL 中,执行 INSERT INTO test VALUES (1) 这类操作时,其内部流程并非直接修改磁盘上的数据文件,而是遵循以下顺序:

  1. 将变更操作生成一条 WAL 记录,写入 WAL Buffer(共享内存)。
  2. 修改共享缓冲区(Shared Buffer)中的数据页(Data Page)。
  3. 在事务提交(COMMIT)时,强制将事务对应的 WAL 记录从 WAL Buffer 刷入磁盘(WAL File)。
  4. 数据页的刷盘操作(Checkpoint 或 Background Writer)则被推迟到后续适当时机执行。

Data_File Data_Buffer WAL_File WAL_Buffer PostgreSQL Client Data_File Data_Buffer WAL_File WAL_Buffer PostgreSQL Client #mermaid-svg-ZwuOJhTX73upokaU{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ZwuOJhTX73upokaU .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ZwuOJhTX73upokaU .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ZwuOJhTX73upokaU .error-icon{fill:#552222;}#mermaid-svg-ZwuOJhTX73upokaU .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ZwuOJhTX73upokaU .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ZwuOJhTX73upokaU .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ZwuOJhTX73upokaU .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ZwuOJhTX73upokaU .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ZwuOJhTX73upokaU .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ZwuOJhTX73upokaU .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ZwuOJhTX73upokaU .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ZwuOJhTX73upokaU .marker.cross{stroke:#333333;}#mermaid-svg-ZwuOJhTX73upokaU svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ZwuOJhTX73upokaU p{margin:0;}#mermaid-svg-ZwuOJhTX73upokaU .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-ZwuOJhTX73upokaU text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-ZwuOJhTX73upokaU .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-ZwuOJhTX73upokaU .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-ZwuOJhTX73upokaU .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-ZwuOJhTX73upokaU .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-ZwuOJhTX73upokaU #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-ZwuOJhTX73upokaU .sequenceNumber{fill:white;}#mermaid-svg-ZwuOJhTX73upokaU #sequencenumber{fill:#333;}#mermaid-svg-ZwuOJhTX73upokaU #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-ZwuOJhTX73upokaU .messageText{fill:#333;stroke:none;}#mermaid-svg-ZwuOJhTX73upokaU .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-ZwuOJhTX73upokaU .labelText,#mermaid-svg-ZwuOJhTX73upokaU .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-ZwuOJhTX73upokaU .loopText,#mermaid-svg-ZwuOJhTX73upokaU .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-ZwuOJhTX73upokaU .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-ZwuOJhTX73upokaU .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-ZwuOJhTX73upokaU .noteText,#mermaid-svg-ZwuOJhTX73upokaU .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-ZwuOJhTX73upokaU .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-ZwuOJhTX73upokaU .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-ZwuOJhTX73upokaU .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-ZwuOJhTX73upokaU .actorPopupMenu{position:absolute;}#mermaid-svg-ZwuOJhTX73upokaU .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-ZwuOJhTX73upokaU .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-ZwuOJhTX73upokaU .actor-man circle,#mermaid-svg-ZwuOJhTX73upokaU line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-ZwuOJhTX73upokaU :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 数据页延迟写入 BEGIN / INSERT 1. 写入 WAL 记录 2. 修改数据页 COMMIT 3. 刷 WAL 到磁盘 写入 返回成功 4. 后续 Checkpoint 刷脏页

性能优势分析

该机制带来了显著的性能优势,核心在于将随机 I/O 转化为顺序 I/O:

  • 数据文件写入 :数据页分布在磁盘的不同位置,属于随机 I/O,尤其在 HDD 时代成本极高。
  • WAL 日志写入 :WAL 日志采用 追加(Append-Only) 的方式写入,属于顺序 I/O,效率远高于随机 I/O。

通过 WAL 机制,事务提交时只需保证顺序写入 WAL 日志即可,无需等待昂贵的随机 I/O 完成,从而大幅提升了写入吞吐量。

检查点(Checkpoint)

定义与作用

检查点(Checkpoint)是 WAL 日志序列中的一个特定点位。当 Checkpoint 完成时,该检查点之前的所有数据变更(即共享缓冲区中的脏页)均已落盘至数据文件

Checkpoint 的核心作用有两点:

  1. 缩短恢复时间:崩溃恢复时,只需从最近的 Checkpoint 位置开始回放 WAL,无需从头开始。
  2. 回收 WAL 日志:Checkpoint 之前的 WAL 日志文件可被安全移除或归档复用。

触发机制

PostgreSQL 的 Checkpoint 由专门的 Checkpointer 后台进程自动触发,主要受以下参数控制:

参数 默认值 说明
checkpoint_timeout 5min 自动 Checkpoint 的最大时间间隔
max_wal_size 1GB WAL 日志总量的软上限,达到后触发 Checkpoint

此外,以下操作也会触发 Checkpoint:

  • 手动执行 CHECKPOINT 命令。
  • 执行 CREATE DATABASE 等特定 DDL 操作。
  • 执行 pg_start_backup() 时(仅限 15 版本之前)。

控制文件(Control File)与 REDO 起点

控制文件概述

控制文件 pg_control 是一个 8KB 的二进制文件,存储于 $PGDATA/global/ 目录下。它记录了 PostgreSQL 集群运行的关键元信息,包括:

  • 数据库集群状态(in production / shut down)。
  • 最新的 Checkpoint 信息。
  • initdb 时设定的基本参数。

崩溃恢复的起点:REDO Location

pg_control 中存储了一个至关重要的字段:Latest checkpoint's REDO location。这是一个 LSN(Log Sequence Number)值,指明了数据库崩溃恢复时,REDO 操作应从 WAL 中的哪个位置开始回放。

每当 Checkpoint 完成时,该 REDO location 都会被更新为当前 Checkpoint 对应的 LSN 位置。因此,随着 Checkpoint 的持续推进,恢复起点也随之向前推进,从而避免从过远的 WAL 日志开始恢复。

查看控制文件信息

使用 pg_controldata 工具可以查看控制文件的详细内容:

bash 复制代码
# 查看控制文件信息
pg_controldata -D $PGDATA

# 输出示例(节选)
pg_control version number:            1300
Catalog version number:               202307071
Database system identifier:           7295214815432107401
Database cluster state:               in production
Latest checkpoint location:           0/3002E60
Latest checkpoint's REDO location:    0/3002E28
Latest checkpoint's REDO WAL file:    000000010000000000000003

通过 SQL 函数 pg_control_checkpoint() 也可查询检查点信息(PostgreSQL 9.6 及更高版本):

sql 复制代码
SELECT redo_lsn, checkpoint_lsn FROM pg_control_checkpoint();
    redo_lsn    | checkpoint_lsn
----------------+----------------
 0/3002E28      | 0/3002E60
(1 row)

LSN(Log Sequence Number)日志序列号

LSN(Log Sequence Number)是 WAL 日志流中的一个字节偏移量,随着每条新记录的写入而单调递增。

  • 数据类型 :PostgreSQL 提供了专用的 pg_lsn 数据类型用于存储 LSN 值。
  • 表示格式 :LSN 通常表示为两个最多 8 位的十六进制数,用斜杠分隔,例如 0/3002E28
  • 内部存储:LSN 是一个 64 位整数。

LSN 在 PostgreSQL 中应用广泛:

  • 标识 WAL 日志中的具体位置。
  • 计算两个 LSN 之间的 WAL 数据量。
  • 作为流复制中主备同步的位点标识。
  • 作为崩溃恢复的 REDO 起点。

CLOG(Commit Log)事务状态日志

CLOG(Commit Log,提交日志)是 PostgreSQL 并发控制机制的重要组成部分,用于记录所有事务的最终状态。

事务状态

CLOG 为每个事务维护以下四种状态之一:

状态 说明
IN_PROGRESS 事务正在运行中
COMMITTED 事务已提交
ABORTED 事务已回滚
SUB_COMMITTED 子事务已提交

存储位置

CLOG 位于共享内存中,供所有事务处理过程中使用。其持久化文件位于数据目录的 pg_xact 子目录下。

版本说明 :PostgreSQL 10 及以前版本,CLOG 文件位于 pg_clog 目录。官方出于安全考虑(防止被误认为普通日志文件而删除),在 PostgreSQL 10 中将其重命名为 pg_xact

CLOG 在可见性判断中扮演关键角色:查询时需通过 CLOG 判断某事务是否已提交,从而决定该事务产生的数据对当前查询是否可见。

Timeline(时间线)

Timeline(时间线)是 PostgreSQL 用于区分数据库历史分支的机制,主要应用于 Point-In-Time Recovery(PITR,时间点恢复)和流复制场景。

  • 概念类比:类似于科幻作品中的"时间线"概念------当从某个历史时间点恢复并继续运行时,数据库将沿一条新的 Timeline 发展。
  • 标识方式:WAL 文件名中的 8 位数字前缀即代表 Timeline ID。
  • 递增规则 :每当执行备库提升为主库(pg_ctl promote),或完成一次归档恢复时,Timeline 会递增。
  • 恢复控制recovery_target_timeline 参数用于指定恢复到哪条时间线。

崩溃恢复完整流程

当 PostgreSQL 异常崩溃后,重新启动时,系统会自动进入恢复模式。完整流程如下:
#mermaid-svg-0pF8ZdIVMK9rbZWa{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-0pF8ZdIVMK9rbZWa .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-0pF8ZdIVMK9rbZWa .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-0pF8ZdIVMK9rbZWa .error-icon{fill:#552222;}#mermaid-svg-0pF8ZdIVMK9rbZWa .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-0pF8ZdIVMK9rbZWa .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-0pF8ZdIVMK9rbZWa .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-0pF8ZdIVMK9rbZWa .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-0pF8ZdIVMK9rbZWa .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-0pF8ZdIVMK9rbZWa .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-0pF8ZdIVMK9rbZWa .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-0pF8ZdIVMK9rbZWa .marker{fill:#333333;stroke:#333333;}#mermaid-svg-0pF8ZdIVMK9rbZWa .marker.cross{stroke:#333333;}#mermaid-svg-0pF8ZdIVMK9rbZWa svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-0pF8ZdIVMK9rbZWa p{margin:0;}#mermaid-svg-0pF8ZdIVMK9rbZWa .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-0pF8ZdIVMK9rbZWa .cluster-label text{fill:#333;}#mermaid-svg-0pF8ZdIVMK9rbZWa .cluster-label span{color:#333;}#mermaid-svg-0pF8ZdIVMK9rbZWa .cluster-label span p{background-color:transparent;}#mermaid-svg-0pF8ZdIVMK9rbZWa .label text,#mermaid-svg-0pF8ZdIVMK9rbZWa span{fill:#333;color:#333;}#mermaid-svg-0pF8ZdIVMK9rbZWa .node rect,#mermaid-svg-0pF8ZdIVMK9rbZWa .node circle,#mermaid-svg-0pF8ZdIVMK9rbZWa .node ellipse,#mermaid-svg-0pF8ZdIVMK9rbZWa .node polygon,#mermaid-svg-0pF8ZdIVMK9rbZWa .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-0pF8ZdIVMK9rbZWa .rough-node .label text,#mermaid-svg-0pF8ZdIVMK9rbZWa .node .label text,#mermaid-svg-0pF8ZdIVMK9rbZWa .image-shape .label,#mermaid-svg-0pF8ZdIVMK9rbZWa .icon-shape .label{text-anchor:middle;}#mermaid-svg-0pF8ZdIVMK9rbZWa .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-0pF8ZdIVMK9rbZWa .rough-node .label,#mermaid-svg-0pF8ZdIVMK9rbZWa .node .label,#mermaid-svg-0pF8ZdIVMK9rbZWa .image-shape .label,#mermaid-svg-0pF8ZdIVMK9rbZWa .icon-shape .label{text-align:center;}#mermaid-svg-0pF8ZdIVMK9rbZWa .node.clickable{cursor:pointer;}#mermaid-svg-0pF8ZdIVMK9rbZWa .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-0pF8ZdIVMK9rbZWa .arrowheadPath{fill:#333333;}#mermaid-svg-0pF8ZdIVMK9rbZWa .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-0pF8ZdIVMK9rbZWa .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-0pF8ZdIVMK9rbZWa .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0pF8ZdIVMK9rbZWa .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-0pF8ZdIVMK9rbZWa .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0pF8ZdIVMK9rbZWa .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-0pF8ZdIVMK9rbZWa .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-0pF8ZdIVMK9rbZWa .cluster text{fill:#333;}#mermaid-svg-0pF8ZdIVMK9rbZWa .cluster span{color:#333;}#mermaid-svg-0pF8ZdIVMK9rbZWa div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-0pF8ZdIVMK9rbZWa .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-0pF8ZdIVMK9rbZWa rect.text{fill:none;stroke-width:0;}#mermaid-svg-0pF8ZdIVMK9rbZWa .icon-shape,#mermaid-svg-0pF8ZdIVMK9rbZWa .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0pF8ZdIVMK9rbZWa .icon-shape p,#mermaid-svg-0pF8ZdIVMK9rbZWa .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-0pF8ZdIVMK9rbZWa .icon-shape .label rect,#mermaid-svg-0pF8ZdIVMK9rbZWa .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0pF8ZdIVMK9rbZWa .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-0pF8ZdIVMK9rbZWa .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-0pF8ZdIVMK9rbZWa :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 正常关闭
异常崩溃
数据库启动
读取 pg_control
检查数据库状态
正常启动
进入自动恢复模式
获取 Latest checkpoint's REDO location
从该 LSN 开始扫描 WAL
REDO: 重放 WAL 记录
恢复完成,数据库进入正常运行状态

实践演示

以下通过实际操作演示崩溃恢复过程(以 PostgreSQL 17.4 为例):

步骤一:查看当前 REDO location

sql 复制代码
SELECT redo_lsn FROM pg_control_checkpoint();
    redo_lsn
----------------
 0/A16F288
(1 row)

步骤二:模拟崩溃(暴力 kill)

bash 复制代码
# 获取 postgres 进程 PID
ps aux | grep postgres | grep -v grep
# 假设 PID 为 4676
kill -9 4676

步骤三:观察启动日志中的恢复信息

bash 复制代码
pg_ctl -D /path/to/17/data start -l logfile
cat logfile

# 日志关键输出示例
LOG:  database system was not properly shut down; automatic recovery in progress
LOG:  redo starts at 0/A16F288
LOG:  consistent recovery state reached at 0/A16F300
LOG:  database system is ready to accept connections

日志中的 redo starts at 0/A16F288pg_control_checkpoint() 查询到的 REDO location 完全一致。数据库正是从该 LSN 点位开始回放 WAL 日志,完成数据恢复。

关键配置参数调优建议

在生产环境中,合理配置 Checkpoint 相关参数对 RTO(Recovery Time Objective,恢复时间目标)有直接影响:

  • checkpoint_timeout 调大:若设为 30 分钟,崩溃恢复时需从 30 分钟前的 Checkpoint 开始回放,恢复时间更长,RTO 增大。
  • checkpoint_timeout 调小:Checkpoint 更频繁,恢复起点更新,RTO 更小,但会带来额外的 I/O 开销。
  • max_wal_size:控制 WAL 总量上限,间接影响 Checkpoint 频率。

实践中需根据业务对 RTO 和 I/O 负载的容忍度进行权衡,通常建议在 5min 到 30min 之间根据实际场景调整。

注意archive_timeout 用于控制 WAL 归档频率,与 Checkpoint 参数是独立的,调整时应区分对待。

API 速览

pg_control_checkpoint()

  • 所属:PostgreSQL 系统函数
  • 适用版本:PostgreSQL 9.6 及更高版本
  • 方法签名pg_control_checkpoint() RETURNS record
  • 返回值 :包含检查点相关信息的记录行,主要字段包括:
    • checkpoint_lsn:检查点自身的 LSN
    • redo_lsn:REDO 操作的起始 LSN
    • timeline_id:当前时间线 ID
  • 示例
sql 复制代码
SELECT * FROM pg_control_checkpoint();
-[ RECORD 1 ]-----+-------------
checkpoint_lsn     | 0/3002E60
redo_lsn           | 0/3002E28
timeline_id        | 1
prev_timeline_id   | 1
full_page_writes   | t

pg_lsn 数据类型

  • 所属:PostgreSQL 内置数据类型
  • 说明:用于存储 LSN(Log Sequence Number)值
  • 内部表示:64 位整数
  • 显示格式 :两个最多 8 位的十六进制数,用斜杠分隔,如 0/3002E28
  • 示例
sql 复制代码
-- 声明 LSN 类型的变量
SELECT '0/3002E28'::pg_lsn;

-- 比较两个 LSN 的大小
SELECT '0/3002E28'::pg_lsn > '0/3002E00'::pg_lsn;  -- true

-- 计算两个 LSN 之间的字节差
SELECT pg_lsn '0/3002E28' - pg_lsn '0/3002E00';
 ?column?
----------
       40

pg_waldump 命令行工具

  • 所属:PostgreSQL 贡献包(contrib)
  • 适用版本:PostgreSQL 9.3 及更高版本
  • 方法签名pg_waldump [option...] [startseg] [endseg]
  • 常用选项
    • -p, --path=PATH:WAL 目录路径
    • -s, --start=LSN:起始 LSN
    • -e, --end=LSN:结束 LSN
    • -t, --timeline=TLI:指定时间线
  • 示例
bash 复制代码
# 解析指定 WAL 文件的内容
pg_waldump -p $PGDATA/pg_wal 000000010000000000000003

# 从指定 LSN 开始解析
pg_waldump -s 0/3002E28 -p $PGDATA/pg_wal

输出示例中可看到每条 WAL 记录的详细信息,包括事务 ID(tx)、操作类型(如 INSERT)、操作的数据块引用(blkref)等。

pg_walinspect 扩展(PostgreSQL 14+)

  • 所属:PostgreSQL 贡献包(contrib)
  • 适用版本:PostgreSQL 14 及更高版本
  • 说明 :提供 SQL 函数接口用于检查 WAL 内容,是 pg_waldump 的 SQL 替代方案
  • 核心函数
    • pg_get_wal_records_info(start_lsn pg_lsn, end_lsn pg_lsn)
    • pg_get_wal_stats(start_lsn pg_lsn, end_lsn pg_lsn, per_record boolean)
  • 示例
sql 复制代码
-- 启用扩展
CREATE EXTENSION pg_walinspect;

-- 查询指定 LSN 范围内的 WAL 记录信息
SELECT * FROM pg_get_wal_records_info('0/3002E28', '0/3002E60');

pg_start_backup() / pg_stop_backup()(已弃用)

  • 所属:PostgreSQL 系统函数
  • 适用版本:PostgreSQL 8.0 至 14(15 版本起移除排他性备份模式)
  • 说明 :用于执行排他性在线备份,在备份期间会生成 backup_label 文件,该文件会影响崩溃恢复行为。
  • 历史背景 :由于排他性备份可能导致恢复时的复杂性,PostgreSQL 社区在 15 版本移除了该模式,推荐使用非排他性备份(pg_basebackup)。

Demo 简单示例

以下使用 Node.js 配合 pg 库,演示 WAL 相关信息的查询与 Checkpoint 的触发。

运行说明

  1. 确保本地已安装 PostgreSQL(14 及以上版本)并运行。
  2. 初始化项目并安装依赖:
bash 复制代码
mkdir pg-wal-demo && cd pg-wal-demo
npm init -y
npm install pg
  1. 将以下代码保存为 index.js
  2. 根据实际数据库配置修改连接参数。
  3. 执行 node index.js

代码说明

js 复制代码
const { Client } = require('pg');

// PostgreSQL 连接配置
const config = {
    host: 'localhost',
    port: 5432,
    database: 'postgres',
    user: 'postgres',
    password: 'your_password'
};

async function main() {
    const client = new Client(config);
    await client.connect();

    console.log('=== PostgreSQL WAL / Checkpoint 信息查询 ===\n');

    // 1. 查询 pg_control_checkpoint()
    console.log('【1】pg_control_checkpoint() 查询结果:');
    const res1 = await client.query('SELECT * FROM pg_control_checkpoint();');
    console.table(res1.rows[0]);

    // 2. 查询当前 LSN 相关系统信息
    console.log('\n【2】当前 LSN 与 WAL 位置:');
    const res2 = await client.query(`
        SELECT
            pg_current_wal_lsn() AS current_wal_lsn,
            pg_walfile_name(pg_current_wal_lsn()) AS current_wal_file,
            pg_current_wal_insert_lsn() AS insert_lsn,
            pg_current_wal_flush_lsn() AS flush_lsn
    `);
    console.table(res2.rows[0]);

    // 3. 手动触发 Checkpoint(需要超级用户权限)
    console.log('\n【3】手动触发 Checkpoint...');
    await client.query('CHECKPOINT;');
    console.log('Checkpoint 执行完成');

    // 4. 再次查询 redo_lsn,观察变化
    console.log('\n【4】Checkpoint 后重新查询 redo_lsn:');
    const res4 = await client.query('SELECT redo_lsn FROM pg_control_checkpoint();');
    console.log(`redo_lsn: ${res4.rows[0].redo_lsn}`);

    // 5. 查询 WAL 统计信息(PostgreSQL 14+,需启用 pg_walinspect)
    try {
        console.log('\n【5】尝试查询 WAL 统计信息 (需 pg_walinspect 扩展):');
        await client.query('CREATE EXTENSION IF NOT EXISTS pg_walinspect;');
        const res5 = await client.query(`
            SELECT COUNT(*) AS record_count
            FROM pg_get_wal_records_info(
                pg_current_wal_lsn() - 1024 * 1024,  -- 最近 1MB 范围
                pg_current_wal_lsn()
            )
        `);
        console.log(`最近 1MB WAL 中的记录数: ${res5.rows[0].record_count}`);
    } catch (err) {
        console.log('pg_walinspect 不可用,跳过 (需要 PostgreSQL 14+ 且已编译 contrib)');
    }

    await client.end();
}

main().catch(console.error);

对应的 PostgreSQL 原生指令

sql 复制代码
-- 1. 查询检查点信息
SELECT * FROM pg_control_checkpoint();

-- 2. 查询当前 WAL LSN 位置
SELECT
    pg_current_wal_lsn() AS current_wal_lsn,
    pg_walfile_name(pg_current_wal_lsn()) AS current_wal_file,
    pg_current_wal_insert_lsn() AS insert_lsn,
    pg_current_wal_flush_lsn() AS flush_lsn;

-- 3. 手动触发 Checkpoint
CHECKPOINT;

-- 4. 再次查询 redo_lsn
SELECT redo_lsn FROM pg_control_checkpoint();

-- 5. 启用 pg_walinspect 并查询 WAL 记录(PostgreSQL 14+)
CREATE EXTENSION IF NOT EXISTS pg_walinspect;
SELECT COUNT(*) FROM pg_get_wal_records_info(
    pg_current_wal_lsn() - 1024 * 1024,
    pg_current_wal_lsn()
);

技术点总结

  • pg_control_checkpoint():获取当前检查点的 REDO 起始位置,是理解崩溃恢复起点的核心 API。
  • pg_current_wal_lsn() 系列函数:实时查看当前 WAL 的写入、刷盘位置。
  • CHECKPOINT 命令:手动触发检查点,验证 redo_lsn 的更新行为。
  • pg_walinspect 扩展:SQL 接口级别的 WAL 内容检查(PostgreSQL 14+)。

多语言示例

以下分别提供 Go、Python 和 Java 的完整实现,用于查询 PostgreSQL 的 WAL 与 Checkpoint 信息,功能与前述 Node.js 版本完全一致。所有示例均使用对应生态中最主流的驱动/库(Go 使用 pgx/v5,Python 使用 psycopg2,Java 使用 JDBC)。

运行说明

  1. 确保本地 PostgreSQL(14+)已运行,并创建测试数据库及用户(如 postgres)。
  2. 修改各语言代码中的连接参数(host、port、database、user、password)。
  3. 依次执行各语言程序,观察输出。

Go 示例(使用 pgx/v5)

go 复制代码
package main

import (
    "context"
    "fmt"
    "log"

    "github.com/jackc/pgx/v5"
)

func main() {
    // 连接配置
    connStr := "postgres://postgres:your_password@localhost:5432/postgres"
    conn, err := pgx.Connect(context.Background(), connStr)
    if err != nil {
        log.Fatal("连接失败:", err)
    }
    defer conn.Close(context.Background())

    fmt.Println("=== PostgreSQL WAL / Checkpoint 信息查询 (Go) ===\n")

    // 1. 查询 pg_control_checkpoint()
    fmt.Println("【1】pg_control_checkpoint() 查询结果:")
    var checkpointLsn, redoLsn string
    var timelineId, prevTimelineId int
    var fullPageWrites bool
    err = conn.QueryRow(context.Background(),
        `SELECT checkpoint_lsn, redo_lsn, timeline_id, prev_timeline_id, full_page_writes
         FROM pg_control_checkpoint()`).
        Scan(&checkpointLsn, &redoLsn, &timelineId, &prevTimelineId, &fullPageWrites)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("checkpoint_lsn: %s\nredo_lsn: %s\ntimeline_id: %d\nprev_timeline_id: %d\nfull_page_writes: %t\n\n",
        checkpointLsn, redoLsn, timelineId, prevTimelineId, fullPageWrites)

    // 2. 查询当前 LSN 与 WAL 位置
    fmt.Println("【2】当前 LSN 与 WAL 位置:")
    var currentLsn, currentFile, insertLsn, flushLsn string
    err = conn.QueryRow(context.Background(), `
        SELECT
            pg_current_wal_lsn(),
            pg_walfile_name(pg_current_wal_lsn()),
            pg_current_wal_insert_lsn(),
            pg_current_wal_flush_lsn()
    `).Scan(&currentLsn, &currentFile, &insertLsn, &flushLsn)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("current_wal_lsn: %s\ncurrent_wal_file: %s\ninsert_lsn: %s\nflush_lsn: %s\n\n",
        currentLsn, currentFile, insertLsn, flushLsn)

    // 3. 手动触发 Checkpoint
    fmt.Println("【3】手动触发 Checkpoint...")
    _, err = conn.Exec(context.Background(), "CHECKPOINT")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("Checkpoint 执行完成\n")

    // 4. 再次查询 redo_lsn
    fmt.Println("【4】Checkpoint 后重新查询 redo_lsn:")
    err = conn.QueryRow(context.Background(), "SELECT redo_lsn FROM pg_control_checkpoint()").Scan(&redoLsn)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("redo_lsn: %s\n\n", redoLsn)

    // 5. 尝试查询 WAL 统计信息 (需 pg_walinspect)
    fmt.Println("【5】尝试查询 WAL 统计信息 (需 pg_walinspect 扩展):")
    _, err = conn.Exec(context.Background(), "CREATE EXTENSION IF NOT EXISTS pg_walinspect")
    if err != nil {
        fmt.Println("pg_walinspect 不可用,跳过 (需要 PostgreSQL 14+ 且已编译 contrib)")
        return
    }
    var count int
    err = conn.QueryRow(context.Background(), `
        SELECT COUNT(*)
        FROM pg_get_wal_records_info(
            pg_current_wal_lsn() - 1024 * 1024,
            pg_current_wal_lsn()
        )
    `).Scan(&count)
    if err != nil {
        fmt.Println("查询 WAL 统计失败:", err)
    } else {
        fmt.Printf("最近 1MB WAL 中的记录数: %d\n", count)
    }
}

对应的 PostgreSQL 原生指令(与 Go 代码逻辑相同):

sql 复制代码
-- 1. 查询检查点信息
SELECT checkpoint_lsn, redo_lsn, timeline_id, prev_timeline_id, full_page_writes
FROM pg_control_checkpoint();

-- 2. 查询当前 WAL LSN 位置
SELECT
    pg_current_wal_lsn(),
    pg_walfile_name(pg_current_wal_lsn()),
    pg_current_wal_insert_lsn(),
    pg_current_wal_flush_lsn();

-- 3. 手动触发 Checkpoint
CHECKPOINT;

-- 4. 再次查询 redo_lsn
SELECT redo_lsn FROM pg_control_checkpoint();

-- 5. 启用 pg_walinspect 并查询 WAL 记录
CREATE EXTENSION IF NOT EXISTS pg_walinspect;
SELECT COUNT(*) FROM pg_get_wal_records_info(
    pg_current_wal_lsn() - 1024 * 1024,
    pg_current_wal_lsn()
);

Python 示例(使用 psycopg2)

python 复制代码
import psycopg2
from psycopg2 import sql

def main():
    # 连接配置
    conn = psycopg2.connect(
        host="localhost",
        port=5432,
        database="postgres",
        user="postgres",
        password="your_password"
    )
    cur = conn.cursor()

    print("=== PostgreSQL WAL / Checkpoint 信息查询 (Python) ===\n")

    # 1. 查询 pg_control_checkpoint()
    print("【1】pg_control_checkpoint() 查询结果:")
    cur.execute("""
        SELECT checkpoint_lsn, redo_lsn, timeline_id, prev_timeline_id, full_page_writes
        FROM pg_control_checkpoint()
    """)
    row = cur.fetchone()
    print(f"checkpoint_lsn: {row[0]}")
    print(f"redo_lsn: {row[1]}")
    print(f"timeline_id: {row[2]}")
    print(f"prev_timeline_id: {row[3]}")
    print(f"full_page_writes: {row[4]}\n")

    # 2. 查询当前 LSN 与 WAL 位置
    print("【2】当前 LSN 与 WAL 位置:")
    cur.execute("""
        SELECT
            pg_current_wal_lsn(),
            pg_walfile_name(pg_current_wal_lsn()),
            pg_current_wal_insert_lsn(),
            pg_current_wal_flush_lsn()
    """)
    row = cur.fetchone()
    print(f"current_wal_lsn: {row[0]}")
    print(f"current_wal_file: {row[1]}")
    print(f"insert_lsn: {row[2]}")
    print(f"flush_lsn: {row[3]}\n")

    # 3. 手动触发 Checkpoint
    print("【3】手动触发 Checkpoint...")
    cur.execute("CHECKPOINT")
    conn.commit()
    print("Checkpoint 执行完成\n")

    # 4. 再次查询 redo_lsn
    print("【4】Checkpoint 后重新查询 redo_lsn:")
    cur.execute("SELECT redo_lsn FROM pg_control_checkpoint()")
    redo_lsn = cur.fetchone()[0]
    print(f"redo_lsn: {redo_lsn}\n")

    # 5. 尝试查询 WAL 统计信息 (需 pg_walinspect)
    print("【5】尝试查询 WAL 统计信息 (需 pg_walinspect 扩展):")
    try:
        cur.execute("CREATE EXTENSION IF NOT EXISTS pg_walinspect")
        conn.commit()
        cur.execute("""
            SELECT COUNT(*)
            FROM pg_get_wal_records_info(
                pg_current_wal_lsn() - 1024 * 1024,
                pg_current_wal_lsn()
            )
        """)
        count = cur.fetchone()[0]
        print(f"最近 1MB WAL 中的记录数: {count}")
    except Exception as e:
        print("pg_walinspect 不可用,跳过 (需要 PostgreSQL 14+ 且已编译 contrib)")

    cur.close()
    conn.close()

if __name__ == "__main__":
    main()

对应的 PostgreSQL 原生指令(与 Python 代码逻辑相同): 同 Go 部分,不再重复列出。

Java 示例(使用 JDBC)

java 复制代码
import java.sql.*;

public class PgWalDemo {
    public static void main(String[] args) {
        String url = "jdbc:postgresql://localhost:5432/postgres";
        String user = "postgres";
        String password = "your_password";

        try (Connection conn = DriverManager.getConnection(url, user, password)) {
            System.out.println("=== PostgreSQL WAL / Checkpoint 信息查询 (Java) ===\n");

            // 1. 查询 pg_control_checkpoint()
            System.out.println("【1】pg_control_checkpoint() 查询结果:");
            try (Statement stmt = conn.createStatement();
                 ResultSet rs = stmt.executeQuery(
                     "SELECT checkpoint_lsn, redo_lsn, timeline_id, prev_timeline_id, full_page_writes " +
                     "FROM pg_control_checkpoint()")) {
                if (rs.next()) {
                    System.out.println("checkpoint_lsn: " + rs.getString("checkpoint_lsn"));
                    System.out.println("redo_lsn: " + rs.getString("redo_lsn"));
                    System.out.println("timeline_id: " + rs.getInt("timeline_id"));
                    System.out.println("prev_timeline_id: " + rs.getInt("prev_timeline_id"));
                    System.out.println("full_page_writes: " + rs.getBoolean("full_page_writes"));
                }
            }
            System.out.println();

            // 2. 查询当前 LSN 与 WAL 位置
            System.out.println("【2】当前 LSN 与 WAL 位置:");
            try (Statement stmt = conn.createStatement();
                 ResultSet rs = stmt.executeQuery(
                     "SELECT pg_current_wal_lsn(), pg_walfile_name(pg_current_wal_lsn()), " +
                     "pg_current_wal_insert_lsn(), pg_current_wal_flush_lsn()")) {
                if (rs.next()) {
                    System.out.println("current_wal_lsn: " + rs.getString(1));
                    System.out.println("current_wal_file: " + rs.getString(2));
                    System.out.println("insert_lsn: " + rs.getString(3));
                    System.out.println("flush_lsn: " + rs.getString(4));
                }
            }
            System.out.println();

            // 3. 手动触发 Checkpoint
            System.out.println("【3】手动触发 Checkpoint...");
            try (Statement stmt = conn.createStatement()) {
                stmt.execute("CHECKPOINT");
            }
            System.out.println("Checkpoint 执行完成\n");

            // 4. 再次查询 redo_lsn
            System.out.println("【4】Checkpoint 后重新查询 redo_lsn:");
            try (Statement stmt = conn.createStatement();
                 ResultSet rs = stmt.executeQuery("SELECT redo_lsn FROM pg_control_checkpoint()")) {
                if (rs.next()) {
                    System.out.println("redo_lsn: " + rs.getString(1));
                }
            }
            System.out.println();

            // 5. 尝试查询 WAL 统计信息 (需 pg_walinspect)
            System.out.println("【5】尝试查询 WAL 统计信息 (需 pg_walinspect 扩展):");
            try (Statement stmt = conn.createStatement()) {
                stmt.execute("CREATE EXTENSION IF NOT EXISTS pg_walinspect");
                try (ResultSet rs = stmt.executeQuery(
                        "SELECT COUNT(*) FROM pg_get_wal_records_info(" +
                        "pg_current_wal_lsn() - 1024 * 1024, pg_current_wal_lsn())")) {
                    if (rs.next()) {
                        System.out.println("最近 1MB WAL 中的记录数: " + rs.getLong(1));
                    }
                }
            } catch (SQLException e) {
                System.out.println("pg_walinspect 不可用,跳过 (需要 PostgreSQL 14+ 且已编译 contrib)");
            }

        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

对应的 PostgreSQL 原生指令(与 Java 代码逻辑相同): 同 Go 部分,不再重复列出。

多语言对比表格

对比维度 Node.js (pg) Go (pgx/v5) Python (psycopg2) Java (JDBC)
驱动/库 pg (npm) github.com/jackc/pgx/v5 psycopg2 (PyPI) org.postgresql:postgresql (Maven)
连接方式 对象配置 + new Client() DSN 字符串或 pgx.ConnectConfig 关键字参数或 DSN DriverManager.getConnection(url, user, pass)
查询执行 client.query() 返回 Result conn.QueryRow() / conn.Exec() cur.execute() + fetchone() / fetchall() Statement.executeQuery() / execute()
参数占位符 $1, $2 (与 PG 一致) $1, $2 %s$1(使用 sql 模块) ?(但 PG JDBC 支持 $1
事务处理 自动提交(可控制) 自动提交(可控制) 默认自动提交,可设置 autocommit 默认自动提交,可 setAutoCommit(false)
异步支持 原生 async/await 原生 context + goroutine 需配合 asyncioasyncpg 更佳) 需配合 CompletableFuture(非阻塞驱动如 R2DBC
连接池 内置 Pool 内置 pgxpool 第三方 psycopg2.poolSQLAlchemy 第三方 HikariCP / Tomcat JDBC
错误处理 try/catch error 返回值 try/except try/catch (SQLException)
适用场景 快速原型、全栈 JS 项目 高并发、微服务、云原生 数据分析、脚本、科学计算 企业级应用、Spring 生态
代码行数(核心逻辑) ~50 行 ~60 行 ~55 行 ~65 行

说明

  • 所有示例均实现了相同的五个步骤:查询检查点信息 → 查询当前 LSN → 手动 Checkpoint → 再次查询 redo_lsn → 查询 WAL 统计(依赖 pg_walinspect)。
  • 错误处理已简化,生产环境需增加重试、超时等机制。
  • Java 示例使用标准的 Statement,实际项目中建议使用 PreparedStatement 或 ORM(如 Hibernate)以提升安全性和可维护性。
  • 各语言对应的 PostgreSQL 原生指令完全一致,便于对照调试。

项目难点与解决方案

核心难点

  • WAL 日志与数据文件的一致性问题:在崩溃恢复场景下,如何确保 REDO 操作从正确的 LSN 点位开始,既不丢失已提交事务的数据,也不重复应用已落盘的变更。
  • CLOG 与 WAL 的协同恢复:崩溃时 CLOG 可能尚未刷盘,恢复后需通过 WAL 重建事务状态,保证可见性判断的正确性。
  • 时间线切换时的恢复路径选择:在 PITR 或主备切换场景下,如何正确选择恢复目标时间线,避免数据分叉。

解决方案

  • Checkpoint + REDO Location 机制 :通过定期 Checkpoint 将脏页落盘,并在 pg_control 中记录 REDO 起点,确保恢复时只回放必要的 WAL 段。
  • WAL 的原子性记录:每条 WAL 记录包含足够的信息(事务 ID、操作类型、数据块引用),保证 REDO 操作的幂等性。
  • Timeline 机制:通过 Timeline ID 区分数据库的不同历史分支,在恢复时明确指定目标时间线,避免数据混乱。

广度

本文覆盖了 PostgreSQL 崩溃恢复的完整技术栈,包括 WAL 机制、Checkpoint、控制文件、LSN、CLOG、Timeline 等多个核心组件,涵盖了从原理到实践的全链路知识点。

深度

深入剖析了 WAL 的顺序写入优势、Checkpoint 的触发与 REDO Location 的更新机制、控制文件在恢复流程中的关键作用,并通过实际演示验证了崩溃恢复的完整流程。

复杂度

本文涉及多个系统组件的协同工作(WAL Writer、Checkpointer、Background Writer、CLOG 等),需要理解事务提交、日志刷盘、数据页落盘之间的时序关系,属于 PostgreSQL 内核级别的复杂话题。

官方文档

参考链接

总结

本文围绕 PostgreSQL 的 WAL(Write-Ahead Logging)预写日志机制及其崩溃恢复原理,系统性地梳理了从理论到实践的核心技术栈。现将关键知识点总结如下:

  • WAL 核心原则:数据页的变更必须在对应的日志记录落盘后才能写入,确保事务持久性。通过顺序追加写入,将随机 I/O 转换为顺序 I/O,显著提升写入性能。
  • Checkpoint 检查点 :定期将共享缓冲区中的脏页刷入数据文件,并更新控制文件中的 Redo LSN,从而缩短崩溃恢复时间并回收旧 WAL 日志。触发条件包括超时(checkpoint_timeout)和 WAL 总量(max_wal_size)等。
  • 控制文件(pg_control):存储数据库状态、最新检查点位置、Redo LSN 等关键元数据。崩溃恢复时,系统从此文件读取 Redo LSN 作为回放起点。
  • LSN(Log Sequence Number) :WAL 日志流的字节偏移量,用于精确定位记录位置。PostgreSQL 提供 pg_lsn 数据类型及相关函数进行管理和计算。
  • CLOG(事务状态日志) :记录每个事务的提交/回滚状态,存储于 pg_xact(PG 10+)目录,是可见性判断的重要依据。
  • Timeline(时间线):用于区分数据库历史分支,在 PITR 和流复制场景中防止数据分叉,每次备库提升或归档恢复时递增。
  • 崩溃恢复流程:启动时检查控制文件状态,若非正常关闭则自动进入恢复模式,从 Redo LSN 开始扫描并重放 WAL 记录,直至数据库达到一致状态。
  • 关键工具pg_controldata 查看控制文件;pg_waldumppg_walinspect(PG 14+)解析 WAL 内容;pg_control_checkpoint() 函数查询检查点信息。
  • 配置调优 :调整 checkpoint_timeoutmax_wal_size 可在 RTO 与 I/O 负载之间取得平衡,生产环境需结合业务容忍度进行设置。

本总结涵盖了 WAL、Checkpoint、控制文件、LSN、CLOG、Timeline 及恢复流程等全部核心内容,所有技术点均基于 PostgreSQL 官方文档(9.6+ 版本),代码示例和演示均可直接复现。

相关推荐
陈聪.1 小时前
企业级 NoSQL 数据库 Redis 核心知识整理
数据库·redis·nosql
翼龙云_cloud2 小时前
腾讯云国际代理商:CDB数据库自动备份和异地灾备配置 从快照到跨区域恢复
运维·数据库·云计算·腾讯云
小屁孩你滑稽掉了2 小时前
prisma操作数据库的方法使用教程(简洁版)
数据库·node.js·prisma·fastify
程序猿炎义2 小时前
【llm-algo-leetcode学习笔记】显存与性能认知底座
笔记·学习·leetcode
l1t2 小时前
利用DuckDB luajit插件和openblas库对表中数据做矩阵运算
数据库·线性代数·矩阵·duckdb
Elastic 中国社区官方博客2 小时前
Elasticsearch:什么是向量数据库?
大数据·运维·数据库·人工智能·elasticsearch·搜索引擎·ai
cspttty2 小时前
财务人员学数据分析考什么证
数据库
摇滚侠3 小时前
《Docker技术入门与实战 第4版》阅读笔记 6 使用 Dockerfile 创建镜像 2
java·笔记·docker
树的枝3 小时前
【STM32】05.TIM定时器
笔记·stm32·单片机·嵌入式硬件