PostgreSQL笔记23:Checkpoint机制与脏页处理深度解析

postgresql 如何手动清理归档文件(pg_wal) 背景 发现数据库宕机,排查原因发现数据磁盘爆满,经排查发现是数据库数据目录下,pg_wal目录占用过大导致。 pg_wal(Write-AHead Logging) 说明 wal日志位置: $PGDATA/pg_wal(pg10之前叫pg_xlog) # #wal日志文件命名规则: #我们看到的wal日志是这样的:000000010000000100000092 #其中前8位:00000001表示timeline; #中间8位:00000001表示logid; #最后8位:00000092表示log 阅读详情

纲要

  • Checkpoint(检查点)机制
    • Checkpoint 的核心作用
    • Checkpoint 的触发条件
    • Checkpoint 的执行流程
  • 脏页(Dirty Page)处理
    • 写入流程与 WAL(预写式日志)
    • 脏页刷盘机制
  • WAL 日志回收
    • 回收策略与 min_wal_size / max_wal_size
    • wal_recycle 参数
  • FPW(全页写,Full Page Write)
    • FPW 的设计背景
    • FPW 对性能的影响与监控
  • 崩溃恢复(Crash Recovery)
    • Redo 点与 Checkpoint 位置
    • pg_control 文件中的关键信息
  • 监控视图
    • pg_stat_checkpointer(PostgreSQL 17+)
    • pg_stat_bgwriter(PostgreSQL 16 及更早版本)

写入流程回顾

在深入 Checkpoint 机制之前,有必要先回顾 PostgreSQL 的核心写入流程。

PostgreSQL 采用 WAL(Write-Ahead Logging,预写式日志) 机制。当数据发生变更时,系统会执行以下操作:

  1. 在 WAL 缓冲区中生成一条日志记录,描述此次变更。
  2. 同时修改共享缓冲区(Shared Buffers)中的对应数据页,将其标记为脏页(Dirty Page)。
  3. 事务提交(COMMIT)时,仅要求 WAL 日志落盘,而非数据页落盘。

由于 WAL 日志采用顺序追加写入,而数据页的写入是随机 I/O,这种设计显著提升了写入性能。事务提交时只需确保 WAL 日志已持久化,即便数据库随后崩溃,也可以通过重放 WAL 日志恢复已提交的事务。

写入流程

数据变更

生成WAL记录

修改Shared Buffers
标记为脏页

WAL Buffer

Shared Buffers
脏页

事务提交

WAL日志落盘

返回提交成功

然而,这种设计也引出了一个关键问题:数据页不可能永远延迟落盘。如果脏页长期不写入磁盘,WAL 日志将持续增长,最终耗尽磁盘空间。PostgreSQL 通过 Checkpoint 机制来解决这一问题。

Checkpoint 机制

Checkpoint 的核心作用

Checkpoint(检查点)是事务序列中的一个特定点,在该点上,所有在检查点之前写入 WAL 的变更都已持久化到数据文件中。

Checkpoint 主要完成两件事:

  1. 刷脏:将 Shared Buffers 中的所有脏页(包括数据页、WAL 缓冲区、CLOG 缓冲区等)写入磁盘。
  2. WAL 回收:在刷脏完成后,Checkpoint 会回收不再需要的旧 WAL 日志段,防止 pg_wal 目录无限膨胀。

触发Checkpoint

写入Redo记录到WAL

刷脏:将所有脏页写入磁盘

Fsync:确保所有变更文件持久化

写入Checkpoint记录到WAL

回收/删除不再需要的WAL段

Checkpoint完成

Checkpoint 的触发条件

PostgreSQL 源码中定义了多种 Checkpoint 触发类型:

触发类型说明
CHECKPOINT_IS_SHUTDOWN正常停机(pg_stop / smart / fast 模式)
CHECKPOINT_END_OF_RECOVERY恢复结束
CHECKPOINT_IMMEDIATE手动执行 CHECKPOINT 命令
CHECKPOINT_FORCE强制检查点
CHECKPOINT_WAIT等待型检查点
CHECKPOINT_REQUESTED请求型检查点
CHECKPOINT_CAUSE_XLOGmax_wal_size 即将超出
CHECKPOINT_CAUSE_TIMEcheckpoint_timeout 超时

在实际运行中,Checkpoint 主要由以下两种条件自动触发:

  • 时间阈值:checkpoint_timeout 参数控制,默认 5 分钟。
  • 空间阈值:max_wal_size 参数控制,默认 1 GB。

两者以先到者为准。如果自上次 Checkpoint 以来没有写入任何 WAL,即使 checkpoint_timeout 已过,新的 Checkpoint 也会被跳过。

此外,数据库超级用户也可以通过 CHECKPOINT 命令手动触发检查点:

CHECKPOINT;

Checkpoint 的执行流程

Checkpoint 的执行是一个渐进过程,而非瞬时操作:

  1. 建立 Redo 点:在 WAL 中写入一条 Redo 记录,标记恢复的起始位置。
  2. 刷脏:将截至 Redo 点累积的所有脏页写入磁盘。
  3. Fsync:确保所有变更文件持久化。
  4. 写入 Checkpoint 记录:在 WAL 中写入一条特殊的 Checkpoint 记录。
  5. 回收 WAL:回收 Redo 点之前的 WAL 段。
pg_control 数据文件 Shared Buffers WAL日志 pg_control 数据文件 Shared Buffers WAL日志 Checkpoint开始 Checkpoint完成 写入Redo记录 刷脏(所有脏页) Fsync 写入Checkpoint记录 回收旧WAL段 更新latest_checkpoint_location

关键参数说明

参数默认值说明
checkpoint_timeout5min两次自动检查点之间的最大时间间隔
max_wal_size1GBWAL 总大小的软上限,达到后触发检查点
min_wal_size80MBWAL 回收后保留的最小大小
checkpoint_completion_target0.9检查点完成时间占检查点间隔的比例
checkpoint_warning30s检查点间隔过短时的告警阈值
wal_recycleon是否回收旧 WAL 段(重命名复用)
full_page_writeson是否启用全页写

checkpoint_completion_target 用于平滑检查点的 I/O 负载。默认值 0.9 意味着检查点将在下一个检查点开始前完成 90% 的工作,将 I/O 峰值分散到整个检查点间隔内。

脏页处理机制

脏页的生成

脏页是指 Shared Buffers 中被修改过但尚未写入磁盘的数据页。每次数据变更(INSERT、UPDATE、DELETE)都会在 Shared Buffers 中产生脏页。

脏页的刷盘途径

脏页通过以下途径写入磁盘:

  1. Checkpointer 进程:在 Checkpoint 时刷写所有脏页。
  2. BgWriter 进程(后台写进程):在后台持续刷写脏页,保持有足够的干净缓冲区可用。
  3. 后端进程:当需要获取缓冲区但无可用干净缓冲区时,后端进程自行刷写脏页。

脏页刷盘途径

Shared Buffers中的脏页

Checkpointer进程

BgWriter进程

后端进程

磁盘

BgWriter 与 Checkpointer 的分工

  • BgWriter(后台写进程) :持续运行,定期将脏页写入磁盘,目的是平滑 I/O 负载,避免在 Checkpoint 时产生大量 I/O 突发。
  • Checkpointer(检查点进程) :在 Checkpoint 触发时,负责将所有脏页刷盘并推进 WAL 回收。

两者协同工作,确保脏页既不会无限累积,也不会因频繁刷盘而影响性能。

WAL 日志回收

回收机制

WAL 日志存储在 pg_wal 目录中,每个段文件默认 16 MB。随着数据库运行,WAL 段会不断增长。Checkpoint 完成后,Redo 点之前的 WAL 段不再需要,可以被回收或删除。

回收(Recycle) 是指将旧的 WAL 段文件重命名,作为新的段文件复用。这避免了频繁创建新文件的系统开销。

-- 查看当前WAL段文件
SELECT pg_ls_waldir() LIMIT 10;

回收策略

PostgreSQL 通过以下参数控制 WAL 回收策略:

  • min_wal_size:回收后 pg_wal 目录保留的最小大小。
  • max_wal_size:pg_wal 目录的软上限。
  • wal_recycle:是否启用 WAL 段回收(默认开启)。

当 WAL 总大小超过 max_wal_size 时,Checkpoint 会触发并回收旧的 WAL 段,将 pg_wal 大小控制回 min_wal_size 附近。

关于 wal_recycle

wal_recycle 参数控制是否将旧的 WAL 段文件重命名以供复用。如果设置为 off,旧的 WAL 段将被直接删除而非回收。在大多数生产环境中,建议保持默认值 on。

FPW(全页写,Full Page Write)

设计背景

PostgreSQL 的数据页默认大小为 8 KB,而操作系统的页缓存通常为 4 KB,磁盘的扇区大小为 512 字节。当 PostgreSQL 将一个 8 KB 的数据页写入磁盘时,可能发生以下情况:

  • 前 4 KB 写入成功
  • 后 4 KB 写入失败(操作系统崩溃或断电)

这会导致数据页不完整(部分写入),即所谓的"断裂页"(Torn Page)。

FPW 机制

为了解决这一问题,PostgreSQL 引入了 FPW(Full Page Write,全页写) 机制:

在每次 Checkpoint 之后,对数据页的第一次修改,会将整个数据页(8 KB)的镜像记录到 WAL 中。

这样,即使发生部分写入,崩溃恢复时也可以从 WAL 中读取完整的页镜像进行覆盖恢复。

Checkpoint完成

数据页首次变更

将整个8KB页镜像写入WAL

后续变更仅写入增量记录

下一次Checkpoint

重置: 下次首次变更再次写入全页

FPW 对性能的影响

FPW 虽然保证了数据一致性,但也带来了显著的性能开销:

  1. WAL 膨胀:每个数据页的首次变更都会产生 8 KB 的 WAL 记录,远大于增量记录的几十到几百字节。
  2. 写入效率下降:大量 WAL 写入占用 I/O 带宽,影响整体吞吐量。
  3. Checkpoint 频率影响:Checkpoint 越频繁,FPW 产生的 WAL 越多。

监控 FPW 占比

可以通过 pg_stat_bgwriter(PostgreSQL 16 及更早)或 pg_stat_checkpointer(PostgreSQL 17+)视图监控 FPW 情况。

-- PostgreSQL 16 及更早版本
SELECT 
    buffers_checkpoint,
    buffers_clean,
    maxwritten_clean,
    buffers_backend,
    buffers_alloc
FROM pg_stat_bgwriter;

-- 计算FPW占比(近似)
-- 如果 buffers_checkpoint 远大于 buffers_clean,说明Checkpoint承担了主要的刷脏工作
-- FPW导致的WAL膨胀可以通过WAL段大小增长率来间接评估
-- PostgreSQL 17+ 
SELECT 
    num_timed,
    num_requested,
    write_time,
    sync_time,
    buffers_written
FROM pg_stat_checkpointer;

pg_stat_checkpointer 视图在 PostgreSQL 17 中引入,将原本属于 pg_stat_bgwriter 的检查点统计信息独立出来。

如果发现 FPW 占比过高(例如 WAL 中超过 50% 是 FPW 记录),建议考虑:

  • 适当调大 checkpoint_timeout 和 max_wal_size,降低 Checkpoint 频率。
  • 评估是否可以在确保数据安全的前提下调整 full_page_writes 参数。

崩溃恢复与 Checkpoint

Redo 点与 Checkpoint 位置

在崩溃恢复时,PostgreSQL 需要确定从 WAL 的哪个位置开始重放。这涉及两个关键概念:

  • Redo 点(Redo Record) :Checkpoint 开始时写入 WAL 的位置。恢复从该点开始。
  • Checkpoint 位置(Checkpoint Location) :Checkpoint 完成时写入 WAL 的位置。

这两个位置都记录在 pg_control 文件中。

崩溃恢复

崩溃

读取最新Checkpoint记录

从Redo点开始重放WAL

恢复完成

WAL时间线

Redo点
Checkpoint开始

刷脏过程

Checkpoint点
Checkpoint完成

后续WAL记录

pg_control 文件中的关键字段:

  • latest_checkpoint_location:最近一次 Checkpoint 记录的位置。
  • latest_checkpoint_redo_location:最近一次 Checkpoint 对应的 Redo 点位置。

恢复时,系统从 Redo 点开始重放 WAL,直到 WAL 末尾,确保所有已提交事务的变更都被应用到数据文件中。

pg_control_checkpoint() 函数

PostgreSQL 9.6 及以上版本提供了 pg_control_checkpoint() 函数,用于查看当前 Checkpoint 状态:

SELECT * FROM pg_control_checkpoint();

监控与诊断

pg_stat_checkpointer(PostgreSQL 17+)

PostgreSQL 17 引入了专用的检查点统计视图:

SELECT 
    num_timed,          -- 由超时触发的检查点次数
    num_requested,      -- 由请求触发的检查点次数
    write_time,         -- 刷脏耗时(毫秒)
    sync_time,          -- Fsync耗时(毫秒)
    buffers_written,    -- 写入的缓冲区数量
    stats_reset         -- 统计信息重置时间
FROM pg_stat_checkpointer;
-- 重置统计信息
SELECT pg_stat_reset_shared('checkpointer');

pg_stat_bgwriter(PostgreSQL 16 及更早)

在 PostgreSQL 16 及更早版本中,检查点统计信息包含在 pg_stat_bgwriter 视图中:

SELECT 
    checkpoints_timed,      -- 由超时触发的检查点次数
    checkpoints_req,        -- 由请求触发的检查点次数
    checkpoint_write_time,  -- 刷脏耗时(毫秒)
    checkpoint_sync_time,   -- Fsync耗时(毫秒)
    buffers_checkpoint      -- 检查点期间写入的缓冲区数量
FROM pg_stat_bgwriter;

日志监控

启用 log_checkpoints 参数后,PostgreSQL 会在日志中记录每次 Checkpoint 的详细信息:

ALTER SYSTEM SET log_checkpoints = on;
SELECT pg_reload_conf();

典型日志输出示例:

LOG: checkpoint starting: time
LOG: checkpoint complete: wrote 155 buffers (0.1%); 0 WAL file(s) added, 0 removed, 0 recycled; write=15.810 s, sync=0.013 s, total=15.904 s; sync files=63, longest=0.003 s, average=0.000 s; distance=783 kB, estimate=2315 kB

通过分析这些日志,可以评估 Checkpoint 的耗时和 I/O 压力,进而调优相关参数。

API 速览

1. CHECKPOINT 命令

所属:PostgreSQL SQL 命令

语法:

CHECKPOINT;

说明:强制执行一个即时检查点,无需等待定时检查点。仅超级用户可执行。

示例:

-- 手动触发检查点
CHECKPOINT;

-- 在事务中执行
BEGIN;
CHECKPOINT;
COMMIT;

2. pg_control_checkpoint()

所属:PostgreSQL 系统函数(9.6+)

签名:pg_control_checkpoint() RETURNS record

返回值:包含当前检查点状态信息的记录。

示例:

SELECT * FROM pg_control_checkpoint();

-- 输出示例:
-- checkpoint_location | redo_location | ...
-- --------------------+---------------+-----
-- 0/302ECB0          | 0/302E9A0     | ...

3. pg_stat_checkpointer

所属:PostgreSQL 系统统计视图(17+)

列定义:

列名类型说明
num_timedbigint由超时触发的检查点次数
num_requestedbigint由请求触发的检查点次数
write_timedouble precision刷脏总耗时(毫秒)
sync_timedouble precisionFsync总耗时(毫秒)
buffers_writtenbigint写入的缓冲区总数
stats_resettimestamp with time zone统计信息重置时间

示例:

-- 查看检查点统计
SELECT * FROM pg_stat_checkpointer;

-- 重置统计
SELECT pg_stat_reset_shared('checkpointer');

4. pg_stat_bgwriter

所属:PostgreSQL 系统统计视图(所有支持版本)

列定义(检查点相关):

列名类型说明
checkpoints_timedbigint由超时触发的检查点次数
checkpoints_reqbigint由请求触发的检查点次数
checkpoint_write_timedouble precision刷脏总耗时(毫秒)
checkpoint_sync_timedouble precisionFsync总耗时(毫秒)
buffers_checkpointbigint检查点期间写入的缓冲区数量

示例:

SELECT 
    checkpoints_timed,
    checkpoints_req,
    checkpoint_write_time,
    checkpoint_sync_time,
    buffers_checkpoint
FROM pg_stat_bgwriter;

5. pg_ls_waldir()

所属:PostgreSQL 系统函数

签名:pg_ls_waldir() RETURNS SETOF record

返回值:pg_wal 目录中的文件列表。

示例:

SELECT name, size, modification 
FROM pg_ls_waldir() 
ORDER BY modification DESC 
LIMIT 10;

Demo 简单示例

以下是一个使用 Node.js 和 pg 驱动演示 Checkpoint 监控的完整示例。

运行说明

环境要求:

  • Node.js 16+
  • PostgreSQL 17+(推荐,以支持 pg_stat_checkpointer)
  • pg 驱动

安装依赖:

npm init -y
npm install pg

配置数据库:
确保 PostgreSQL 正在运行,并创建一个测试数据库:

CREATE DATABASE checkpoint_demo;

代码说明

import { Client } from 'pg';

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

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

    try {
        // 1. 创建测试表
        await client.query(`
            CREATE TABLE IF NOT EXISTS checkpoint_test (
                id SERIAL PRIMARY KEY,
                data TEXT,
                created_at TIMESTAMP DEFAULT NOW()
            )
        `);
        console.log('✅ 测试表已就绪');

        // 2. 查看当前检查点统计(PostgreSQL 17+)
        const statsBefore = await client.query(`
            SELECT 
                num_timed,
                num_requested,
                write_time,
                sync_time,
                buffers_written
            FROM pg_stat_checkpointer
        `);
        console.log('📊 检查点统计(操作前):');
        console.table(statsBefore.rows[0]);

        // 3. 生成一些写入负载
        console.log('📝 生成写入负载...');
        const insertPromises = [];
        for (let i = 0; i < 10000; i++) {
            insertPromises.push(
                client.query('INSERT INTO checkpoint_test (data) VALUES ($1)', [
                    `Test data ${i}`
                ])
            );
        }
        await Promise.all(insertPromises);
        console.log('✅ 写入负载完成');

        // 4. 手动触发 Checkpoint
        console.log('🔄 手动触发 Checkpoint...');
        await client.query('CHECKPOINT');
        console.log('✅ Checkpoint 完成');

        // 5. 查看更新后的检查点统计
        const statsAfter = await client.query(`
            SELECT 
                num_timed,
                num_requested,
                write_time,
                sync_time,
                buffers_written
            FROM pg_stat_checkpointer
        `);
        console.log('📊 检查点统计(操作后):');
        console.table(statsAfter.rows[0]);

        // 6. 查看 WAL 使用情况
        const walInfo = await client.query(`
            SELECT 
                pg_wal_lsn_diff(pg_current_wal_lsn(), '0/0') AS total_wal_bytes,
                pg_wal_lsn_diff(
                    pg_current_wal_lsn(), 
                    pg_control_checkpoint().redo_location
                ) AS wal_since_redo_bytes
        `);
        console.log('📁 WAL 信息:');
        console.log(`  总 WAL 字节数: ${walInfo.rows[0].total_wal_bytes}`);
        console.log(`  自 Redo 点以来 WAL 字节数: ${walInfo.rows[0].wal_since_redo_bytes}`);

        // 7. 清理
        await client.query('DROP TABLE checkpoint_test');
        console.log('🧹 清理完成');

    } catch (err) {
        console.error('❌ 错误:', err);
    } finally {
        await client.end();
    }
}

main();

/* ============================================================
   PostgreSQL 原生指令注释
   ============================================================

   -- 查看检查点统计 (PostgreSQL 17+)
   SELECT num_timed, num_requested, write_time, sync_time, buffers_written 
   FROM pg_stat_checkpointer;

   -- 查看检查点统计 (PostgreSQL 16 及更早)
   SELECT checkpoints_timed, checkpoints_req, checkpoint_write_time, 
          checkpoint_sync_time, buffers_checkpoint 
   FROM pg_stat_bgwriter;

   -- 手动触发检查点
   CHECKPOINT;

   -- 查看当前 WAL LSN
   SELECT pg_current_wal_lsn();

   -- 查看 Checkpoint 控制信息
   SELECT * FROM pg_control_checkpoint();

   -- 查看 WAL 段文件
   SELECT name, size, modification FROM pg_ls_waldir() ORDER BY modification DESC;

   -- 启用检查点日志
   ALTER SYSTEM SET log_checkpoints = on;
   SELECT pg_reload_conf();
*/

技术点总结

本 Demo 演示了以下核心技术:

  1. Checkpoint 手动触发:使用 CHECKPOINT 命令强制执行检查点。
  2. 检查点统计监控:通过 pg_stat_checkpointer 视图查看检查点执行情况。
  3. WAL 位置查询:使用 pg_current_wal_lsn() 和 pg_control_checkpoint() 获取 WAL 位置信息。
  4. 写入负载模拟:通过批量插入生成脏页,触发检查点刷脏。
  5. 统计信息对比:对比操作前后的检查点统计,观察 buffers_written 等指标的变化。

多语言示例

基于前文的 Node.js 示例,以下分别提供 Go、Python 和 Java 语言的完整实现,均使用对应的 PostgreSQL 驱动,执行相同的操作流程:创建测试表、查看检查点统计、生成写入负载、手动触发 Checkpoint、查看更新后的统计和 WAL 信息、清理表。

Go 语言示例

使用 pgx 驱动(推荐)或 lib/pq,本示例采用 pgx/v5。

运行说明

环境要求:

  • Go 1.19+
  • PostgreSQL 17+(推荐以支持 pg_stat_checkpointer)
  • 安装依赖:go get github.com/jackc/pgx/v5

配置数据库:与 Node.js 示例相同,创建 checkpoint_demo 数据库。

代码说明
package main

import (
    "context"
    "fmt"
    "log"
    "os"
    "time"

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

func main() {
    ctx := context.Background()

    // 连接配置
    connStr := "postgres://postgres:your_password@localhost:5432/checkpoint_demo"
    conn, err := pgx.Connect(ctx, connStr)
    if err != nil {
        log.Fatalf("无法连接数据库: %v", err)
    }
    defer conn.Close(ctx)

    // 1. 创建测试表
    _, err = conn.Exec(ctx, `
        CREATE TABLE IF NOT EXISTS checkpoint_test (
            id SERIAL PRIMARY KEY,
            data TEXT,
            created_at TIMESTAMP DEFAULT NOW()
        )
    `)
    if err != nil {
        log.Fatalf("创建表失败: %v", err)
    }
    fmt.Println("✅ 测试表已就绪")

    // 2. 查看当前检查点统计 (PostgreSQL 17+)
    var numTimed, numRequested int64
    var writeTime, syncTime float64
    var buffersWritten int64
    err = conn.QueryRow(ctx, `
        SELECT num_timed, num_requested, write_time, sync_time, buffers_written
        FROM pg_stat_checkpointer
    `).Scan(&numTimed, &numRequested, &writeTime, &syncTime, &buffersWritten)
    if err != nil {
        log.Fatalf("查询统计失败: %v", err)
    }
    fmt.Println("📊 检查点统计(操作前):")
    fmt.Printf("  num_timed=%d, num_requested=%d, write_time=%.2fms, sync_time=%.2fms, buffers_written=%d\n",
        numTimed, numRequested, writeTime, syncTime, buffersWritten)

    // 3. 生成写入负载
    fmt.Println("📝 生成写入负载...")
    batch := &pgx.Batch{}
    for i := 0; i < 10000; i++ {
        batch.Queue("INSERT INTO checkpoint_test (data) VALUES ($1)", fmt.Sprintf("Test data %d", i))
    }
    br := conn.SendBatch(ctx, batch)
    // 必须消费完所有结果
    for i := 0; i < batch.Len(); i++ {
        _, err = br.Exec()
        if err != nil {
            log.Fatalf("批量插入失败: %v", err)
        }
    }
    br.Close()
    fmt.Println("✅ 写入负载完成")

    // 4. 手动触发 Checkpoint
    fmt.Println("🔄 手动触发 Checkpoint...")
    _, err = conn.Exec(ctx, "CHECKPOINT")
    if err != nil {
        log.Fatalf("Checkpoint失败: %v", err)
    }
    fmt.Println("✅ Checkpoint 完成")

    // 5. 查看更新后的检查点统计
    err = conn.QueryRow(ctx, `
        SELECT num_timed, num_requested, write_time, sync_time, buffers_written
        FROM pg_stat_checkpointer
    `).Scan(&numTimed, &numRequested, &writeTime, &syncTime, &buffersWritten)
    if err != nil {
        log.Fatalf("查询统计失败: %v", err)
    }
    fmt.Println("📊 检查点统计(操作后):")
    fmt.Printf("  num_timed=%d, num_requested=%d, write_time=%.2fms, sync_time=%.2fms, buffers_written=%d\n",
        numTimed, numRequested, writeTime, syncTime, buffersWritten)

    // 6. 查看 WAL 使用情况
    var totalWalBytes, walSinceRedoBytes int64
    err = conn.QueryRow(ctx, `
        SELECT
            pg_wal_lsn_diff(pg_current_wal_lsn(), '0/0') AS total_wal_bytes,
            pg_wal_lsn_diff(
                pg_current_wal_lsn(),
                pg_control_checkpoint().redo_location
            ) AS wal_since_redo_bytes
    `).Scan(&totalWalBytes, &walSinceRedoBytes)
    if err != nil {
        log.Fatalf("查询WAL信息失败: %v", err)
    }
    fmt.Println("📁 WAL 信息:")
    fmt.Printf("  总 WAL 字节数: %d\n", totalWalBytes)
    fmt.Printf("  自 Redo 点以来 WAL 字节数: %d\n", walSinceRedoBytes)

    // 7. 清理
    _, err = conn.Exec(ctx, "DROP TABLE checkpoint_test")
    if err != nil {
        log.Fatalf("清理失败: %v", err)
    }
    fmt.Println("🧹 清理完成")
}

// 原生 SQL 注释参见 Node.js 示例中的注释,此处略。
技术点总结
  • 使用 pgx 驱动的 Batch 功能批量执行插入,提高性能。
  • 通过 QueryRow 获取单行统计信息。
  • 手动执行 CHECKPOINT 命令。
  • 使用 pg_control_checkpoint() 获取 Redo 位置。

Python 示例

使用 psycopg2(或 asyncpg),本示例采用 psycopg2 同步版本。

运行说明

环境要求:

  • Python 3.8+
  • PostgreSQL 17+(推荐)
  • 安装依赖:pip install psycopg2-binary
代码说明
import psycopg2
from psycopg2 import sql

def main():
    # 连接配置
    conn = psycopg2.connect(
        host="localhost",
        port=5432,
        database="checkpoint_demo",
        user="postgres",
        password="your_password"
    )
    conn.autocommit = True  # 便于执行CHECKPOINT
    cur = conn.cursor()

    try:
        # 1. 创建测试表
        cur.execute("""
            CREATE TABLE IF NOT EXISTS checkpoint_test (
                id SERIAL PRIMARY KEY,
                data TEXT,
                created_at TIMESTAMP DEFAULT NOW()
            )
        """)
        print("✅ 测试表已就绪")

        # 2. 查看当前检查点统计 (PostgreSQL 17+)
        cur.execute("""
            SELECT num_timed, num_requested, write_time, sync_time, buffers_written
            FROM pg_stat_checkpointer
        """)
        row = cur.fetchone()
        print("📊 检查点统计(操作前):")
        print(f"  num_timed={row[0]}, num_requested={row[1]}, write_time={row[2]}ms, sync_time={row[3]}ms, buffers_written={row[4]}")

        # 3. 生成写入负载
        print("📝 生成写入负载...")
        # 使用批量插入提高效率
        data_list = [(f"Test data {i}",) for i in range(10000)]
        cur.executemany("INSERT INTO checkpoint_test (data) VALUES (%s)", data_list)
        print("✅ 写入负载完成")

        # 4. 手动触发 Checkpoint
        print("🔄 手动触发 Checkpoint...")
        cur.execute("CHECKPOINT")
        print("✅ Checkpoint 完成")

        # 5. 查看更新后的检查点统计
        cur.execute("""
            SELECT num_timed, num_requested, write_time, sync_time, buffers_written
            FROM pg_stat_checkpointer
        """)
        row = cur.fetchone()
        print("📊 检查点统计(操作后):")
        print(f"  num_timed={row[0]}, num_requested={row[1]}, write_time={row[2]}ms, sync_time={row[3]}ms, buffers_written={row[4]}")

        # 6. 查看 WAL 使用情况
        cur.execute("""
            SELECT
                pg_wal_lsn_diff(pg_current_wal_lsn(), '0/0') AS total_wal_bytes,
                pg_wal_lsn_diff(
                    pg_current_wal_lsn(),
                    pg_control_checkpoint().redo_location
                ) AS wal_since_redo_bytes
        """)
        total, since_redo = cur.fetchone()
        print("📁 WAL 信息:")
        print(f"  总 WAL 字节数: {total}")
        print(f"  自 Redo 点以来 WAL 字节数: {since_redo}")

        # 7. 清理
        cur.execute("DROP TABLE checkpoint_test")
        print("🧹 清理完成")

    except Exception as e:
        print(f"❌ 错误: {e}")
    finally:
        cur.close()
        conn.close()

if __name__ == "__main__":
    main()

# 原生 SQL 注释参见 Node.js 示例。
技术点总结
  • 使用 psycopg2 的 executemany 进行批量插入。
  • 设置 autocommit=True 以便于执行 CHECKPOINT(因为 CHECKPOINT 不能在事务块中执行)。
  • 使用游标获取单行和多行结果。
  • 统计信息查询与 Node.js 一致。

Java 示例

使用 JDBC 驱动(org.postgresql:postgresql)。

运行说明

环境要求:

  • Java 11+
  • PostgreSQL 17+(推荐)
  • Maven 或 Gradle 引入依赖:org.postgresql:postgresql:42.6.0

Maven 依赖:

<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>42.6.0</version>
</dependency>
代码说明
import java.sql.*;

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

        try (Connection conn = DriverManager.getConnection(url, user, password)) {
            conn.setAutoCommit(true); // 允许执行CHECKPOINT

            // 1. 创建测试表
            try (Statement stmt = conn.createStatement()) {
                stmt.execute("""
                    CREATE TABLE IF NOT EXISTS checkpoint_test (
                        id SERIAL PRIMARY KEY,
                        data TEXT,
                        created_at TIMESTAMP DEFAULT NOW()
                    )
                """);
                System.out.println("✅ 测试表已就绪");
            }

            // 2. 查看当前检查点统计 (PostgreSQL 17+)
            String queryStats = "SELECT num_timed, num_requested, write_time, sync_time, buffers_written FROM pg_stat_checkpointer";
            try (PreparedStatement pstmt = conn.prepareStatement(queryStats);
                 ResultSet rs = pstmt.executeQuery()) {
                if (rs.next()) {
                    System.out.println("📊 检查点统计(操作前):");
                    System.out.printf("  num_timed=%d, num_requested=%d, write_time=%.2fms, sync_time=%.2fms, buffers_written=%d%n",
                            rs.getLong("num_timed"),
                            rs.getLong("num_requested"),
                            rs.getDouble("write_time"),
                            rs.getDouble("sync_time"),
                            rs.getLong("buffers_written"));
                }
            }

            // 3. 生成写入负载 (使用批量插入)
            System.out.println("📝 生成写入负载...");
            String insertSQL = "INSERT INTO checkpoint_test (data) VALUES (?)";
            try (PreparedStatement pstmt = conn.prepareStatement(insertSQL)) {
                for (int i = 0; i < 10000; i++) {
                    pstmt.setString(1, "Test data " + i);
                    pstmt.addBatch();
                    if (i % 1000 == 0) {
                        pstmt.executeBatch(); // 每1000条提交一次
                    }
                }
                pstmt.executeBatch();
            }
            System.out.println("✅ 写入负载完成");

            // 4. 手动触发 Checkpoint
            System.out.println("🔄 手动触发 Checkpoint...");
            try (Statement stmt = conn.createStatement()) {
                stmt.execute("CHECKPOINT");
            }
            System.out.println("✅ Checkpoint 完成");

            // 5. 查看更新后的检查点统计
            try (PreparedStatement pstmt = conn.prepareStatement(queryStats);
                 ResultSet rs = pstmt.executeQuery()) {
                if (rs.next()) {
                    System.out.println("📊 检查点统计(操作后):");
                    System.out.printf("  num_timed=%d, num_requested=%d, write_time=%.2fms, sync_time=%.2fms, buffers_written=%d%n",
                            rs.getLong("num_timed"),
                            rs.getLong("num_requested"),
                            rs.getDouble("write_time"),
                            rs.getDouble("sync_time"),
                            rs.getLong("buffers_written"));
                }
            }

            // 6. 查看 WAL 使用情况
            String walQuery = """
                    SELECT
                        pg_wal_lsn_diff(pg_current_wal_lsn(), '0/0') AS total_wal_bytes,
                        pg_wal_lsn_diff(
                            pg_current_wal_lsn(),
                            pg_control_checkpoint().redo_location
                        ) AS wal_since_redo_bytes
                    """;
            try (PreparedStatement pstmt = conn.prepareStatement(walQuery);
                 ResultSet rs = pstmt.executeQuery()) {
                if (rs.next()) {
                    long totalWalBytes = rs.getLong("total_wal_bytes");
                    long walSinceRedoBytes = rs.getLong("wal_since_redo_bytes");
                    System.out.println("📁 WAL 信息:");
                    System.out.println("  总 WAL 字节数: " + totalWalBytes);
                    System.out.println("  自 Redo 点以来 WAL 字节数: " + walSinceRedoBytes);
                }
            }

            // 7. 清理
            try (Statement stmt = conn.createStatement()) {
                stmt.execute("DROP TABLE checkpoint_test");
                System.out.println("🧹 清理完成");
            }

        } catch (SQLException e) {
            System.err.println("❌ 错误: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

// 原生 SQL 注释参见 Node.js 示例。
技术点总结
  • 使用 JDBC 批量操作(addBatch / executeBatch)提高插入效率。
  • 设置 setAutoCommit(true) 以便执行 CHECKPOINT。
  • 使用 PreparedStatement 和 ResultSet 查询统计信息。
  • 统计信息查询与 Node.js 一致。

多语言对比

特性Node.js (pg)Go (pgx)Python (psycopg2)Java (JDBC)
驱动/库pgpgx/v5psycopg2postgresql JDBC
连接方式new Client(config)pgx.Connect(ctx, connStr)psycopg2.connect(...)DriverManager.getConnection(url, user, password)
批量插入Promise.all 或 INSERT 多个值pgx.BatchexecutemanyaddBatch / executeBatch
执行CHECKPOINTclient.query('CHECKPOINT')conn.Exec(ctx, "CHECKPOINT")cur.execute("CHECKPOINT")stmt.execute("CHECKPOINT")
查询单行client.query(...).rows[0]conn.QueryRow(...).Scan(...)cur.fetchone()rs.next() 后 rs.getXxx(...)
查询多行/批量结果rows 数组Batch 消费fetchall 或遍历ResultSet 遍历
事务控制默认自动提交,可手动 BEGIN默认自动提交,可手动默认自动提交(autocommit=True)默认自动提交(setAutoCommit(true))
异步支持原生 async/await原生 context 支持同步(有异步版 asyncpg)同步(有异步版 vertx-pg-client 等)
错误处理try/catch返回 errortry/excepttry/catch (SQLException)
依赖管理npmgo modpipMaven/Gradle
适用场景JavaScript/TypeScript 全栈高性能微服务数据科学/脚本企业级后端

选择建议:

  • Node.js:适合全栈 JavaScript 团队,异步非阻塞 I/O 优势明显。
  • Go:适合追求高并发、低延迟的场景,编译型语言,部署简单。
  • Python:适合快速原型开发、数据分析结合数据库操作的场景。
  • Java:适合大型企业级应用,丰富的生态和成熟的 ORM(如 Hibernate)集成。

官方文档

参考链接

总结

本文深入解析了 PostgreSQL 的 Checkpoint 机制与脏页处理流程,涵盖以下核心技术要点:

  • WAL 机制:事务提交仅需 WAL 落盘,数据页延迟写入,通过顺序写提升性能。
  • Checkpoint 核心作用:刷脏(将所有脏页写入磁盘)和 WAL 回收(防止 pg_wal 无限膨胀)。
  • Checkpoint 触发条件:checkpoint_timeout(默认 5 分钟)和 max_wal_size(默认 1 GB),以先到者为准。
  • 脏页刷盘途径:Checkpointer 进程(检查点时)、BgWriter 进程(后台持续)和后端进程(紧急情况)。
  • WAL 回收策略:通过 min_wal_size、max_wal_size 和 wal_recycle 控制,旧段文件被重命名复用。
  • FPW(全页写) :Checkpoint 后首次变更的数据页整页写入 WAL,防止部分写入导致的数据损坏,但会带来 WAL 膨胀和性能开销。
  • 崩溃恢复:从 Redo 点开始重放 WAL,pg_control 文件记录 Checkpoint 位置和 Redo 位置。
  • 监控视图:pg_stat_checkpointer(PostgreSQL 17+)和 pg_stat_bgwriter(PostgreSQL 16 及更早)提供检查点执行统计。

合理配置 Checkpoint 相关参数是 PostgreSQL 性能调优的重要环节。过于频繁的 Checkpoint 会增加 I/O 负载和 FPW 开销,而过于稀疏的 Checkpoint 则会导致崩溃恢复时间变长。生产环境中应结合业务负载和 RTO 要求,通过监控数据持续优化 checkpoint_timeout、max_wal_size 和 checkpoint_completion_target 等参数。

PostgreSQL源码分析——CHECKPOINT 在此之前的WAL日志,对应的Buffer中的脏页需要全部刷盘,也就是说,在此位置之前的WAL日志是不需要进行回放的,因为页已经落盘了。关于重做点,重做点其实就是当前WAL日志的插入点,或者说当前WAL日志记录的终点位置,从这个位置开始,之前的所有WAL日志对应的脏页刷盘,然后清理WAL日志,在进行checkpoint的过程中,其实业务是不停的,还会继续源源不断的产生WAL日志,后续产生的WAL日志就是从重做点开始的,而这部分WAL日志对于的脏页并没有被刷盘,所以崩溃恢复时,需要从这个重做点开始进行回放。 阅读详情

相关推荐

数据库Checkpoint机制原理与生产调优实战

Checkpoint是数据库保障数据持久性(Durability)与一致性(Consistency)的核心机制,本质是通过定期固化内存脏页状态并记录WAL起始位点,构建可验证、可重建的恢复基线。其原理依赖WAL日志与状态快照的协同——WAL记录变更动作,Checkpoint定义状态起点,二者共同实现崩溃后‘按图索骥’式快速恢复。该机制在PostgreSQL、MySQL等关系型数据库及Flink等流处理系统中广泛应用,技术价值在于显著压缩恢复时间、规避IO风暴、支撑高可用架构。典型应用场景包括主库宕机秒级重启

weixin_30455023的博客 407

postgresql的pg_wal日志

个人整理,来源: https://developer.aliyun.com/article/694169 https://www.cnblogs.com/VicLiu/p/11854782.html https://blog.youkuaiyun.com/weixin_39540651/article/details/105737136 https://www.cnblogs.com/VicLiu/p/11854782.html 网络上还存在大量的帖子,关于pg_log,xlog,clog 刚刚接触PG.

Aiky哇 8650

数据库Checkpoint原理与实战:从PostgreSQL到Flink的精准状态恢复

Checkpoint是现代数据系统实现崩溃恢复、精确回滚和容错计算的核心机制,其本质是在特定时间点对内存状态、日志位置与元数据的一致性封存。它不同于文件级备份,依赖WAL(预写日志)序列号(LSN)、原子刷盘和校验锚点(如pg_control)保障确定性。技术价值在于支撑PITR(时间点恢复)、Exactly-Once流处理语义及HTB等场景下的内核级故障复现。典型应用场景涵盖PostgreSQL生产调优、Flink状态后端配置、以及基于pg_control与WAL的手动恢复工程。本文聚焦checkpoin

weixin_34133829的博客 381

PostgreSQL检查点(checkpoint)详解

checkpoint简单点说就是一个数据库事件,用来保证数据一致性和完整性。 当我们在数据库中执行checkpoint时,就会将其之前的脏数据刷到磁盘,从而实现数据缩短数据库崩溃恢复时间的目的。 例如当我们更新任何数据的时候,对应于包含该数据的块将在shared buffers中更新。所做更改的记录保存在wal缓冲区中。提交后,我们所做的更改将被写入磁盘上的wal文件,从而使它们永久生效。如果实例立即崩溃,则将清除数据库共享缓冲区,但是在重新启动数据库时,PostgreSQL将把WAL文件中记录的更改应用于

Focus on PostgreSQL 1万+

PostgreSQL底层原理:MVCC、WAL与查询优化全解析

关系型数据库(RDBMS)的核心能力源于ACID事务保障与多版本并发控制(MVCC)机制,而PostgreSQL作为开源RDBMS的标杆,其可靠性与高性能并非来自黑盒封装,而是由WAL日志、共享缓冲区、事务快照、元组可见性检查等底层组件协同实现。理解MVCC如何通过xmin/xmax字段管理数据版本,掌握WAL写前日志如何确保崩溃恢复的一致性,结合EXPLAIN ANALYZE中的Buffers指标诊断I/O瓶颈,是构建高并发、低延迟数据库系统的工程基础。本文深入PostgreSQL进程模型、内存结构与物理

weixin_30294295的博客 431

Patroni高可用生产部署:Linux内核、etcd与配置深度调优

PostgreSQL高可用依赖于底层协调服务与操作系统协同保障,Patroni作为声明式HA管理器,其可靠性不来自自身逻辑复杂度,而源于对etcd一致性、Linux系统参数、PostgreSQL状态反馈机制的精准控制。理解Raft共识超时、WAL同步延迟、文件句柄限制与NTP时钟偏差等基础原理,是避免脑裂、双主、假死等故障的前提。技术价值体现在将分布式协调、内核I/O调度、网络栈调优与数据库生命周期管理统一纳入可观测、可验证、可回滚的工程实践体系。典型应用场景包括金融核心账务库、实时风控平台及混合云多活架构

weixin_33696822的博客 1662

PostgreSQL数据库WAL总结

简单地说,WAL的核心概念是,对数据文件(表和索引所在的地方)的更改必须在写入了日志文件后这些更改之后才可以写入数据文件,也就是说,描述更改的日志记录被刷新到永久存储之后才可以写数据文件。使用WAL可以显著减少磁盘写操作的数量,因为只需要将日志文件刷新到磁盘以确保提交事务,而不是事务更改的每个数据文件。日志文件是按顺序写入的,因此同步日志的成本要比刷新数据页的成本低得多。备注:数据库将脏数据刷到数据文件上,这个动作是随机I/O,性能比写日志的顺序I/O差太多总结

qq_58883939的博客 4105

PostgreSQL向量搜索实战:pgvector部署调优与语义检索集成

向量搜索是AI应用中实现语义检索的核心能力,其本质是在高维空间中计算相似度并高效召回。技术原理上依赖距离度量(如余弦距离、欧氏距离)与近似最近邻索引(如HNSW、IVFFlat),以平衡精度与性能。pgvector作为PostgreSQL原生向量扩展,将该能力无缝嵌入成熟关系数据库,避免多系统协同的运维复杂度与数据一致性风险。它支持SQL原生查询、ACID事务、统一权限与监控,显著降低LLM应用集成门槛。本文聚焦生产级落地,涵盖pgvector安装适配、向量表设计、索引参数调优(lists/probes/m

weixin_30307921的博客 567

PostgreSQL16.3在国产系统openEuler23.09的性能调优实战:shared_buffers与WAL配置详解

本文详细解析了在国产操作系统openEuler23.09上对PostgreSQL16.3进行性能调优的实战方法。文章聚焦于两个核心配置:shared_buffers(共享缓冲区)与WAL(预写式日志),深入探讨了其调优原理、参数设置策略及与操作系统的协同优化,旨在帮助DBA在国产化环境中充分释放数据库性能潜力,提升系统吞吐量与稳定性。

linux的博客 703

DeepAgents深度搜索实战:Harness边界与可控性工程

深度搜索(Deep Search)是一种超越关键词匹配的智能信息检索范式,其核心在于多步规划、上下文管理与工具协同。它依赖LangGraph状态机实现可审计的执行流,通过Harness机制封装沙箱隔离、内存压缩与人工干预等可控性能力。技术价值体现在对PDF等非结构化文档的精准定位、表格提取与版本比对,广泛应用于技术文档分析、合规审查与知识库构建等场景。本文聚焦DeepAgents框架中planning with files、DockerSandbox、PersistentMemory与Human-in-th

weixin_33738982的博客 332

CentOS 7 上部署 TimescaleDB 实战指南:PostgreSQL 时序扩展安装与调优

时序数据库是处理物联网、监控和金融时间序列数据的核心基础设施。其底层原理基于关系型数据库的扩展架构,通过超表(Hypertable)、自动分区和连续聚合等关键技术,实现高效写入、高压缩率与低延迟查询。TimescaleDB 作为 PostgreSQL 最成熟的时序扩展,依托原生SQL兼容性与企业级运维能力,在稳定操作系统如 CentOS 7 上展现出极高的工程价值。它无需替换现有数据库栈,即可赋予传统 PostgreSQL 时序专用能力,显著提升海量时间戳数据的存储效率与分析性能。本文聚焦于 CentOS

dggnqzt6462的博客 427

PostgreSQL源码编译安装深度指南:生产级配置与安全合规实践

PostgreSQL编译安装是数据库从可用走向高可靠、高安全、可审计的关键技术环节。其核心在于理解configure参数如何影响内核能力边界,掌握shared_buffers、wal_buffers等关键配置与硬件资源的量化关系,并实现SSL加密、SCRAM-SHA-256认证、systemd服务集成等企业级能力。该过程直接决定版本可控性、等保三级合规性及故障归因效率,广泛应用于金融核心系统、信创政务云与Kubernetes PG镜像构建等场景。本文聚焦PG15.4源码级部署,覆盖依赖校验、多版本隔离、LD

475

PostgreSQL生产环境数据库管理核心实践

PostgreSQL数据库管理远不止SQL操作,而是涵盖元数据、实例、数据库与对象四层的工程化体系。其核心原理在于通过分层控制保障数据一致性、访问可控性与故障可恢复性,技术价值体现在避免误删、性能劣化和跨版本迁移失败等真实风险。典型应用场景包括高并发订单系统运维、金融级数据审计、多租户SaaS权限隔离及RPO/RTO严苛的灾备建设。本文深入解析pg_dump备份策略、pg_basebackup物理恢复、WAL归档机制及autovacuum调优等关键实践,直击生产环境中DROP DATABASE误操作、连接风

weixin_34380948的博客 343

PostgreSQL16.3在openEuler下的性能调优实战:shared_buffers设置与WAL日志优化

本文详细介绍了在openEuler操作系统上对PostgreSQL 16.3进行性能调优的实战方法。核心聚焦于shared_buffers内存分配与WAL日志优化,通过基准测试对比,指导用户根据硬件资源(如32GB内存与NVMe SSD)精细化配置关键参数,有效提升数据库TPS并降低延迟,实现稳定高效运行。

c8d9e0f1的博客 834

Ubuntu 16.04 安装配置 PostgreSQL 9.5 实战指南

PostgreSQL 是一款功能完备、事务强一致的开源关系型数据库,其核心原理基于 MVCC 多版本并发控制与 WAL 预写日志机制,技术价值体现在高可靠性、细粒度权限体系和可扩展的插件生态。典型应用场景包括政企系统迁移、高校实验平台搭建及遗留系统长期运维——尤其在 Ubuntu 16.04 这类稳定但已停止维护的 LTS 发行版上,选择官方源提供的 PostgreSQL 9.5 而非强行升级至 14+,可规避 libc、openssl 等底层依赖冲突风险,保障系统级可控性与安全补丁可审计性。本文聚焦该经典

weixin_33725807的博客 414

3DE重客户端安装

3DE重客户端安装

m0_48170265的博客 9439

Penpot开源设计平台:企业级自托管部署与架构深度解析

在数字化产品开发领域,设计与开发之间的协作断层一直是团队效率的主要瓶颈。Penpot作为业界首个完全开源的设计与原型平台,为企业提供了从设计到代码的无缝协作解决方案。本文将深入解析Penpot的架构设计原理,并提供从零到生产环境的完整部署指南,帮助技术决策者和运维工程师构建安全、可扩展的私有设计协作平台。 ## 价值主张:为什么选择Penpot作为企业设计基础设施 Penpot的核心价值在于其

gitblog_00123的博客 932

智能车实战——极速越野组GPS与惯导融合定位导航全解析

本文深入解析了智能车极速越野组中GPS与惯导模块的融合定位导航技术。通过对比GPS的全局定位与惯导的高频姿态感知,详细阐述了如何利用MM32SPN27主控,将两者数据融合以实现精准、实时的车辆控制。内容涵盖硬件选型、软件数据处理、PID控制及系统调试等实战环节,为智能车开发者提供了一套完整的解决方案。

pz8901234的博客 1204

Odoo服务器搭建避坑指南:Ubuntu+PostgreSQL+Python深度配置

Odoo并非开箱即用的单体应用,而是一个依赖操作系统内核、数据库引擎、运行时环境与Web服务协同工作的企业级ERP框架。其核心原理在于Ubuntu系统层(如locale、swappiness、file-max)、PostgreSQL(shared_buffers、pg_hba.conf、连接池)、Python3(版本锁定、venv隔离)与pip3(源加速、wheel缓存清理)四者必须严格对齐。这种强耦合带来高稳定性,也导致常见‘一键安装’失败——本质是环境链断裂。技术价值体现在生产就绪性:规避内存抖动、DN

weixin_34302561的博客 481

STM32驱动四位共阳极数码管实现高精度秒表计时器

本文详细介绍了如何使用STM32驱动四位共阳极数码管实现高精度秒表计时器。项目基于STM32F103C8T6最小系统板,通过定时器中断和GPIO控制,实现0.1秒精度的计时功能,并解析了共阳极数码管的动态扫描原理及按键消抖等关键技术。

n8m7b6v5c4的博客 951

遗传算法优化光学超表面:智能设计突破纳米结构瓶颈

光学超表面设计本质上是一个高维、非线性、离散每个结构单元(如纳米砖柱、孔洞、十字架)有多个参数(长、宽、高、旋转角、材料),整个阵列的参数组合数量随单元数指数级增长。性能指标(如聚焦效率、工作带宽、偏振转换率、特定角度响应)往往无法用简单解析式表达,需依赖计算密集型电磁仿真(如FDTD, FDFD, RCWA)来评估。结构参数微小变化可能引起光学响应的剧烈跃变,目标函数存在多个局部最优解。实际制造通常要求结构参数取自有限的离散值(如特定刻蚀深度、材料库)。

weixin_51101456的博客 1143

FreshRSS 版本演进深度解读:从 0.7.2 到 1.23.1 的功能迭代、架构变迁与运维要点

> 本文基于仓库内 [docs/CHANGELOG-old2.md](https://gitcode.com/gh_mirrors/fr/FreshRSS/blob/27288e125df86007265a9b37300952d02a81bad8/docs/CHANGELOG-old2.md?utm_source=gitcode_repo_files)("old 2" 号变更日志,与 [CHANG

gitblog_00679的博客 740

YOLOv11涨点改进| CVPR 2026 |独家创新首发、特征融合改进篇| 引入FAAFusion傅里叶角对准融合模块,促进高低频特征融合,增强模型在小目标、密集目标检测和旋转目标检测任务高效涨点

🔥本文提出FAAFusion傅里叶角对准融合模块,用于改进YOLOv11网络模型。该模块通过在特征金字塔融合阶段对齐高低层特征的方向信息,缓解传统FPN直接融合带来的方向冲突问题。FAAFusion利用傅里叶旋转等变性原理,在频域估计主方向并进行特征对齐,显著提升了多尺度特征表达的一致性与有效性。实验表明,该方法在DOTA等遥感数据集上取得了新的最先进水平,特别适用于旋转目标检测任务。文章详细介绍了模块原理、结构优势,并提供了三种不同的YOLOv11改进配置方案和完整的核心代码实现。

Ai缝合怪、助力小伙伴高效发论文 154
上一篇: PostgreSQL笔记22: WAL日志与崩溃恢复原理
下一篇: PostgreSQL笔记24: Background Writer 后台写进程的原理、参数调优与可观测性演进
Wang's Blog
博客等级 码龄12年 7438粉丝 2421原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wang's Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值