openGauss日志系统详解:从WAL到审计日志的故障排查实战

举报
行者·全栈架构师 发表于 2026/09/30 14:42:15 2026/09/30
【摘要】 生产环境出了问题,第一反应就是看日志。但openGauss的日志体系比MySQL复杂得多——有运行日志、WAL日志、审计日志、慢查询日志、WDR报告,五类日志各有不同的用途和排查方法。本文从一次真实的"数据库响应突然变慢"的故障排查出发,演示了如何通过五类日志层层递进定位问题根因。同时也总结了WAL损坏修复、审计日志配置、日志轮转等高频运维场景的操作。

📝 文章摘要:生产环境出了问题,第一反应就是看日志。但 openGauss 的日志体系比 MySQL 复杂得多 —— 有运行日志、WAL 日志、审计日志、慢查询日志、WDR 报告,五类日志各有不同的用途和排查方法。本文从一次真实的"数据库响应突然变慢"的故障排查出发,演示了如何通过五类日志层层递进定位问题根因。同时也总结了 WAL 损坏修复、审计日志配置、日志轮转等高频运维场景的操作。

⏱ 预计阅读时间:16 分钟(全文约 4,600 字)

🎯 一个真实的故障排查场景

2025 年 4 月的一个周二下午,订单创建接口的 P99 延迟从正常的 45ms 飙升到了 3.2 秒,持续了大约 10 分钟。

没有告警、没有报错、连接数正常、CPU 正常 —— 一切指标看起来都正常,但业务就是慢。

这次排查让我把 openGauss 的五类日志全部翻了一遍。下面沿着排查路径来讲每类日志的用途。

🗺️ openGauss 五类日志总览

Mermaid 图表 1

日志位置速查

日志类型 存储位置 默认开启 保留策略
运行日志 $PGDATA/pg_log/ ✅ 按天轮转,保留 30 天
WAL 日志 $PGDATA/pg_wal/ ✅ 自动清理(有复制槽则保留)
审计日志 $PGDATA/pg_audit/ ❌ 需要开启
慢查询日志 pg_log 中标记 ❌ 需要开启 log_min_duration_statement
WDR 快照 系统表 dbe_perf ❌ 需要安装插件

🔍 排查路径:从慢查询到 WAL 的"破案"过程

第一步:运行日志 —— 有没有报错?

# 先看运行日志,有没有 ERROR 或 FATAL
grep -E "ERROR|FATAL|WARNING" $PGDATA/pg_log/postgresql-2025-04-15_*.log | tail -50

运行日志是第一站,因为它记录了所有连接、权限、内存、I/O 相关的错误信息。

当天找到的内容只有一条 WARNING:

2025-04-15 14:23:17 CST WARNING:  checkpointer process (PID 28765) wait for WAL flush for 12.345 seconds

WAL flush 等待了 12 秒 —— 这不太正常,但还不是 ERROR,不会触发告警。

第二步:慢查询日志 —— 谁在慢?

# 开启慢查询日志(临时开启,不需要重启)
gs_guc reload -D $PGDATA -c "log_min_duration_statement = 500"   # 记录超过 500ms 的 SQL

# 过 5 分钟后查看
cat $PGDATA/pg_log/postgresql-2025-04-15_*.log | grep "duration:" | sort -t: -k2 -rn | head -10

输出:

2025-04-15 14:28:31 CST LOG:  duration: 8245.123 ms  statement: UPDATE orders SET status='shipped' WHERE id=1234567
2025-04-15 14:28:29 CST LOG:  duration: 6123.456 ms  statement: UPDATE orders SET status='shipped' WHERE id=1234568
2025-04-15 14:28:27 CST LOG:  duration: 5234.789 ms  statement: UPDATE orders SET status='shipped' WHERE id=1234569

规律:全部是 UPDATE orders SET status='shipped',逐条执行,每条约 5-8 秒。但 id 上有主键索引,按主键更新怎么会慢?

第三步:看等待事件 —— 卡在哪里了?

-- 查看当前会话的等待事件
SELECT 
    pid,
    wait_event_type,
    wait_event,
    state,
    query
FROM pg_stat_activity
WHERE state = 'active'
  AND wait_event IS NOT NULL;

输出:

  pid   | wait_event_type |  wait_event   | state  | query
--------+-----------------+---------------|--------|-------------------------
 28765  | IO              | WALWriteLock  | active | UPDATE orders SET ...
 28766  | IO              | WALWriteLock  | active | UPDATE orders SET ...
 28767  | IO              | WALWriteLock  | active | UPDATE orders SET ...

所有更新都在等 WALWriteLock。


第四步:WAL 分析 —— 为什么 WAL 写不动了?

# 查看 WAL 目录情况
ls -lh $PGDATA/pg_wal/ | head -20

发现 WAL 目录下有大量未归档的文件,而且最近几个 WAL 文件的大小异常:

-rw------- 1 omm omm  64M Apr 15 14:30 0000000100000000000000AB
-rw------- 1 omm omm  64M Apr 15 14:28 0000000100000000000000AC
-rw------- 1 omm omm  64M Apr 15 14:25 0000000100000000000000AD

每 2 分钟写满一个 64MB 的 WAL 段,正常的订单系统大约是 5-8 分钟写满一个。

-- 查看 WAL 生成速率
SELECT 
    pg_size_pretty(
        pg_wal_lsn_diff(pg_current_wal_lsn(), '0/0')::BIGINT
    ) AS total_wal_written;

重启查询对比,发现 WAL 生成速率是平时的 3 倍。

第五步:根因定位 —— 大量 UPDATE 导致 WAL 暴增

继续深挖,发现这些 UPDATE 来自一个刚上线的批量脚本,它在循环更新订单状态:

// 有问题的代码
for (Order order : pendingOrders) {
    // 每次循环都提交一次事务
    jdbcTemplate.update("UPDATE orders SET status = ? WHERE id = ?", 
        "shipped", order.getId());
}

每次循环生成一个独立的事务,每个事务都要写 WAL,10 万笔订单就是 10 万次 WAL 写入。

更有问题的是,status 字段上有一个 B-tree 索引,每次 UPDATE 既要改表数据(写入 WAL),又要改索引(也写入 WAL)。

修复:改为批量提交:

// 修复后:每 1000 条提交一次
int batchSize = 0;
for (Order order : pendingOrders) {
    jdbcTemplate.update("UPDATE orders SET status = ? WHERE id = ?", 
        "shipped", order.getId());
    batchSize++;
    if (batchSize % 1000 == 0) {
        // 手动提交(假设 autoCommit = false)
        connection.commit();
    }
}
connection.commit();  // 提交剩余的

改了之后,WAL 生成速率从 32MB/min 降到了 8MB/min,WALWriteLock 等待消失了,接口延迟恢复到 45ms。


📝 运行日志配置

# postgresql.conf — 运行日志配置
log_destination = 'csvlog'                  # 推荐 CSV 格式,方便导入分析
logging_collector = on
log_directory = 'pg_log'
log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log'
log_file_mode = 0600
log_truncate_on_rotation = off
log_rotation_age = 1d                       # 每天轮转
log_rotation_size = 100MB                   # 或 100MB 轮转
log_min_messages = WARNING                  # 只记录 WARNING 及以上的日志

日志分析技巧

# 1. 统计各类型的日志数量
grep -oP '(?<= )[A-Z]+(?=:)' $PGDATA/pg_log/*.log | sort | uniq -c | sort -rn

# 2. 按小时统计 ERROR 数量
grep "ERROR" $PGDATA/pg_log/*.log | grep -oP '\d{4}-\d{2}-\d{2} \d{2}' | sort | uniq -c

# 3. 找到最耗时的 10 条 SQL
grep "duration:" $PGDATA/pg_log/*.log | \
    sed 's/.*duration: //' | sort -t' ' -k1 -rn | head -10

🔒 审计日志配置

信创合规要求数据库必须有审计日志。openGauss 的审计日志比 MySQL 的 audit log 更细粒度。

# postgresql.conf — 审计日志配置
audit_enabled = on                          # 开启审计
audit_directory = 'pg_audit'
audit_file_mode = 0600
audit_rotation_age = 1d                     # 每天轮转
audit_rotation_size = 200MB

# 审计事件类型
audit_dml_state = on                        # INSERT/UPDATE/DELETE
audit_ddl_state = on                        # CREATE/ALTER/DROP
audit_login_logout = on                     # 登录/登出
audit_special_function = on                 # 特殊函数(如 pg_read_file)
audit_system_function = on                  # 系统函数
audit_user_locked = on                      # 用户锁定事件

# 排除某些用户或表的审计(减少日志量)
audit_whitelist_users = 'monitor,backup'    # 不审计监控用户

审计日志查询

-- 查询审计日志(通过系统函数)
SELECT * FROM pg_query_audit(
    '2025-04-15 00:00:00', 
    '2025-04-15 23:59:59'
) WHERE operation_type = 'INSERT'
ORDER BY entry_id DESC;

审计日志轮转与清理

#!/bin/bash
# /opt/scripts/clean_audit_logs.sh — 保留 90 天

find $PGDATA/pg_audit/ -name "*.csv" -mtime +90 -delete

💥 踩坑:开启审计后性能下降

开启全部审计事件后,TPS 下降了约 15%。原因是每次 DML 操作都要额外写审计日志。

优化方案:

# 只审计关键操作
audit_dml_state = off                       # 关闭常规 DML 审计
audit_ddl_state = on                        # 保留 DDL 审计(表结构变更)
audit_login_logout = on                     # 保留登录审计
audit_special_function = on                 # 保留敏感操作
-- 创建审计策略(针对敏感表单独审计)
CREATE AUDIT POLICY audit_order_policy 
    PRIVILEGES ALL
    WHEN ON TABLE orders
    ADD TO AUDIT LOG;

💥 WAL 损坏修复

这是最让人紧张的场景 —— WAL 文件损坏导致数据库无法启动。

现象

# 启动时遇到这个错误
FATAL:  WAL file is corrupted: invalid record length at 0/AB123456

修复方案

# 1. 先备份损坏的 WAL(以防万一)
cp $PGDATA/pg_wal/0000000100000000000000AB /tmp/

# 2. 确认最新的完整检查点
gs_ctl status -D $PGDATA
# 输出会显示最新的检查点 LSN

# 3. 跳过损坏的 WAL,继续恢复
# 修改 postgresql.conf
recovery_target_time = ''                   # 不指定时间点
ignore_checksum_failure = on                # 跳过校验失败

# 4. 如果还是不行,重置 WAL(会丢失数据,谨慎!)
gs_ctl reset_wal -D $PGDATA -l 0000000100000000000000AB

预防 WAL 损坏的最佳实践

# 1. 开启 WAL 校验
wal_checksums = on                          # 默认就是 on

# 2. 使用可靠的存储(RAID 10 + BBU 缓存)
# 不要用 NFS、不要用 Ceph RBD 做 WAL 存储

# 3. 把 WAL 放到独立的 SSD 上(硬件隔离)
# 配置 postgresql.conf
wal_sync_method = fdatasync                 # 默认
# 或者用 O_DIRECT
# wal_sync_method = open_sync

🔄 日志轮转与磁盘保护

日志写满磁盘导致数据库不可用,是我见过最冤的事故。

#!/bin/bash
# /opt/scripts/rotate_logs.sh — 每天凌晨执行

PGDATA="/data/opengauss/data"
RETENTION_DAYS=30

# 1. 运行日志
find $PGDATA/pg_log/ -name "*.csv" -mtime +$RETENTION_DAYS -delete
find $PGDATA/pg_log/ -name "*.log" -mtime +$RETENTION_DAYS -delete

# 2. 审计日志
find $PGDATA/pg_audit/ -name "*.csv" -mtime +90 -delete

# 3. 检查磁盘水位
DISK_USAGE=$(df -h $PGDATA | tail -1 | awk '{print $5}' | tr -d '%')
if [ "$DISK_USAGE" -gt 80 ]; then
    # 触发告警
    curl -X POST "http://alert.company.com/api/alert" \
        -H "Content-Type: application/json" \
        -d "{\"level\":\"warning\",\"message\":\"数据盘使用率 ${DISK_USAGE}%\"}"
fi

# 4. WAL 目录监控
WAL_SIZE_MB=$(du -sm $PGDATA/pg_wal/ | awk '{print $1}')
if [ "$WAL_SIZE_MB" -gt 10240 ]; then  # 超过 10GB
    # 触发告警(可能复制槽异常)
fi

❓ 常见问题

Q1:运行日志和审计日志的区别是什么?

运行日志 审计日志
目的 排查错误和异常 安全追溯和合规
记录内容 错误、警告、慢查询 谁在什么时间做了什么操作
保留周期 30 天 90 天(或更长)
谁查 DBA、开发 安全审计员、监管机构

Q2:WAL 文件太多怎么办?

-- 检查哪些 WAL 没有被消费
SELECT slot_name, restart_lsn, pg_size_pretty(
    pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)
) AS unconsumed_wal
FROM pg_replication_slots;

如果某个复制槽的 unconsumed_wal 很大,说明对应的备库掉线了。备库恢复前要评估是否删除该复制槽。

Q3:审计日志会不会影响性能?

会。实测全量审计开启后 TPS 下降约 15%。建议只审计 DDL + 登录 + 敏感操作,不要审计所有 DML。

Q4:慢查询日志找不到怎么办?

确认 log_min_duration_statement 是否设置,并且 logging_collector = on 开启了。慢查询日志跟运行日志混在一起的,用 grep "duration:" 过滤。


🔄 日志产生的完整生命周期

从 SQL 执行到 WAL 清理,一条日志数据在 openGauss 内部经历了以下完整流程:

Mermaid 图表 2

生命周期关键节点说明

阶段 进程/组件 说明 排查要点
① SQL 执行 SQL 解析器 → 执行引擎 应用发送 SQL,生成执行计划并修改数据页 pg_stat_activity 查看当前执行
② WAL 写入 WAL 缓冲区 → WAL 文件 事务提交时将 WAL 记录从内存刷到磁盘 WALWriteLock 等待、pg_wal/ 目录大小
③ 检查点 检查点进程 将共享缓冲区脏页刷盘,记录 REDO 起点 检查点频率过高会触发大量 I/O
④ 归档 归档进程 将写满的 WAL 段拷贝到远程存储 归档失败会导致 WAL 积压
⑤ 清理 清理进程 删除已归档且检查点已过的 WAL 文件 复制槽未消费或归档失败会阻止清理

📝 总结

那次 WALWriteLock 的故障排查让我深刻理解了 openGauss 的日志体系:

  1. 运行日志 — 快速定位 ERROR 和 FATAL
  2. 慢查询日志 — 找到那些拖垮性能的"坏 SQL"
  3. 等待事件 — 分析数据库到底"卡"在哪个环节
  4. WAL 分析 — 定位写入瓶颈和数据量异常的根因
  5. 审计日志 — 安全合规必备

日志不是越多越好,而是关键时候找得到有用的。

配置日志时有三个核心原则:

  • 运行日志:记录所有 WARNING 以上,保留 30 天
  • 慢查询日志:记录超过 500ms 的 SQL,用于日常优化
  • 审计日志:只审计 DDL + 登录 + 敏感操作,减少性能损耗

下一篇文章讲 openGauss 集群的日常运维和故障快速恢复,包括备份恢复、巡检脚本、健康检查。

💬 互动:你在数据库故障排查中,最常用到哪类日志?有没有过"日志什么都查不出来,最后发现是 XX 问题"的经历?

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。