记录一些自己关注的知识点,不会每条都记,可能有部分补充,原文请看 postgres-howto
一、 查询性能与观测
1. 建议用 EXPLAIN (ANALYZE, BUFFERS)
可以看到SQL每步实际的IO次数(乘以8K则为字节数),pg 18开始,buffer成为默认值
2. pg_stat_statements速通
- 用途:top sql 宏观分析
- 范围:不包含在执行的语句、执行失败的语句
- 指标:核心为累积指标(calls、total_time、rows 等),部分为统计类指标(stddev_plan_time、stddev_exec_time、min_exec_time)
完整指标:https://www.postgresql.org/docs/current/pgstatstatements.html
- 怎么用:取快照,看两个快照间的差异数据。如果真的分析DB重启至今的数据,可以直接看
- 如今AI已经非常强大,其实更重要的是知道要看什么:给它指标名,想看的数据,我们负责拿分析结果
3. 临时收集会话数据
- 每秒收集一次
pg_stat_activity样本,并无限次地记录日志 (直到手动中断) - 导出的csv文件,原文是导回pg分析,现在更好的方式,直接喂给AI获取目标结果
sql
while sleep 1; do
psql -XAtc "
copy (
with samples as (
select
clock_timestamp(),
clock_timestamp() - xact_start as xact_duration,
*
from pg_stat_activity
)
select *
from samples
where xact_duration > interval '1 minute'
order by xact_duration desc
) to stdout delimiter ',' csv
" 2>&1 \
| tee -a long_tx_$(date +%Y%m%d).log.csv
done
4. 什么查询叫慢查询
最近刚好也被业务同事问到是否有慢查询监控,多慢的语句算慢,会影响业务,其实应该是由业务定义,但作为DBA,可以给出一些建议范围:
OLAP:
- 一般看与均值的差异,均值1小时,某次突发2小时,算慢
- 有增量不用用全量,用增量但没索引,十有八九都算慢
- 会有较频繁DDL的表,如果查询运行分钟至小时级,易高频阻塞业务,一般也算慢
OLTP:
- 10ms以下:高性能
- 100ms以下:推荐的性能
- 100~200ms:若面向工厂流水线等,可以为慢查询
- 1s:若面向用户,基本属于有感的慢查询
高频查询:
- 即使是高性能查询,在过高执行频率下也会导致负载崩溃,需要注意
二、 运维排障
1. pg关闭慢的常见原因
- 存在大事务/长事务
- 大量缓冲区是脏的 ,导致关闭时的检查点时间过长
- WAL 归档 (archive_command) 滞后
- 从库延迟
之前停库也遇到过停不下来的问题,当时是归档异常及存在发送延迟,可以参考
2. pg长时间无法启动如何处理
不该做的事情 (非专家常见的做法):
- 不明所以就开始担心或等待很长时间
- 多次尝试停止/重新启动
该做的事情:
- 保持冷静
首先查看日志,了解它正在做什么
- 了解你的配置和工作负载
配置:max_wal_size和checkpoint_timeout 主要是检查点相关配置,常见于强制关机或从备份恢复
工作负载:主要是wal日志的产生量
- 了解并观察 REDO 进程
比较长,后续单独记一篇学习
3. 如何加速pg_dump
- 压缩
pg_dump ... | gzip
- 不保存到磁盘的转储/恢复
pg_dump -h ... | pg_restore
- 并行化的
pg_dump
通过指定 -j数值 启动 指定数量的并行 pg_dump 进程来加速:
bash
pg_dump -Fd -j8 -f ./test_dump test
- 自定义的高级并行化
pg_dump 的并行化是在表级别进行的,无法并行转储单个表。
要并行转储单个大表,需要使用自定义解决方案。为此,我们需要使用多个 SQL 客户端,如 psql,每个客户端在 REPEATABLE READ 隔离级别下工作 (pg_dump 也是在此隔离级别下工作的,参见文档),且 (十分重要) 所有转储事务需要使用相同的快照。
4. 监控索引创建/重建进度(pg 12开始)
此查询的核心依赖于 pg_stat_progress_create_index(pg 12 引入)
PostgreSQL: Documentation: 17: 27.4. Progress Reporting
配合\watch 5命令,可以循环执行查看进度
sql
select
now(),
query_start as started_at,
now() - query_start as query_duration,
format('[%s] %s', a.pid, a.query) as pid_and_query,
index_relid::regclass as index_name,
relid::regclass as table_name,
(pg_size_pretty(pg_relation_size(relid))) as table_size,
nullif(wait_event_type, '') || ': ' || wait_event as wait_type_and_event,
phase,
format(
'%s (%s of %s)',
coalesce((round(100 * blocks_done::numeric / nullif(blocks_total, 0), 2))::text || '%', 'N/A'),
coalesce(blocks_done::text, '?'),
coalesce(blocks_total::text, '?')
) as blocks_progress,
format(
'%s (%s of %s)',
coalesce((round(100 * tuples_done::numeric / nullif(tuples_total, 0), 2))::text || '%', 'N/A'),
coalesce(tuples_done::text, '?'),
coalesce(tuples_total::text, '?')
) as tuples_progress,
current_locker_pid,
(select nullif(left(query, 150), '') || '...' from pg_stat_activity a where a.pid = current_locker_pid) as current_locker_query,
format(
'%s (%s of %s)',
coalesce((round(100 * lockers_done::numeric / nullif(lockers_total, 0), 2))::text || '%', 'N/A'),
coalesce(lockers_done::text, '?'),
coalesce(lockers_total::text, '?')
) as lockers_progress,
format(
'%s (%s of %s)',
coalesce((round(100 * partitions_done::numeric / nullif(partitions_total, 0), 2))::text || '%', 'N/A'),
coalesce(partitions_done::text, '?'),
coalesce(partitions_total::text, '?')
) as partitions_progress,
(
select
format(
'%s (%s of %s)',
coalesce((round(100 * n_dead_tup::numeric / nullif(reltuples::numeric, 0), 2))::text || '%', 'N/A'),
coalesce(n_dead_tup::text, '?'),
coalesce(reltuples::int8::text, '?')
)
from pg_stat_all_tables t, pg_class tc
where t.relid = p.relid and tc.oid = p.relid
) as table_dead_tuples
from pg_stat_progress_create_index p
left join pg_stat_activity a on a.pid = p.pid
order by p.index_relid
; -- in psql, use "\watch 5" instead of semicolon
未完待续...