PostgreSQL生产环境调参实战:从内存到WAL的关键配置
2026/9/7 19:02:24 网站建设 项目流程

1. 为什么生产环境PostgreSQL一定要重新调参

拿默认配置直接上生产,是我见过最多、也最可惜的翻车方式。PostgreSQL安装完成后的postgresql.conf是为“能跑起来”设计的,不是为“跑得好”设计的。默认的shared_buffers只有128MB,max_connections只有100,work_mem只有4MB,这些参数在家用笔记本上开发调试完全没问题,可一旦面对生产环境的真实流量,性能瓶颈会立刻暴露出来。

我印象很深的一次经历:接手一套运行了半年的业务系统,数据库部署在16核64GB的云主机上,但配置几乎全是默认值。业务高峰期一个报表查询能把CPU打到100%,连带着正常交易接口都跟着超时。后来把shared_buffers从128MB调到12GB,work_mem从4MB调到16MB,什么都没改,高峰期CPU直接降到40%以下。这就是调参的价值——不花一分钱硬件成本,靠合理配置把数据库潜力释放出来。

这篇文章适合谁看?如果你正在准备上线PostgreSQL生产实例,或者已经上线但总觉得性能不对劲,又或者单纯想搞明白postgresql.conf里那些参数到底该怎么填,那这篇文章就是给你写的。我会按内存、查询规划、并发连接、WAL与检查点、日志监控这几个维度,把生产环境最核心的参数一个一个拆开讲清楚,告诉大家默认值是多少、推荐怎么设、为什么这么设、设错了会有什么后果。

2. 内存类参数:PostgreSQL性能的基石

内存参数的设定直接决定了数据库在同等硬件条件下能跑多快。PostgreSQL对内存的管理方式和MySQL不太一样,它没有一个大而全的Buffer Pool统一接管,而是分为共享内存和进程私有内存两部分。理解这个区分,是调好内存参数的前提。

2.1 shared_buffers:整个数据库的共享缓存池

shared_buffers是PostgreSQL最核心的内存参数,它是所有后端进程共享的缓存区,负责缓存数据页和索引页。当查询请求的数据页不在shared_buffers里时,PostgreSQL才会去操作系统磁盘读取。所以这个值越大,数据页命中率越高,磁盘I/O越少。

默认值是128MB,这对生产环境来说非常小。推荐设置为物理内存的25%左右。一个简单的估算表格如下:

物理内存推荐shared_buffers
8GB2GB
16GB4GB
32GB8GB
64GB16GB
128GB32GB
256GB64GB(不建议超过这个值)

为什么不建议超过64GB?因为shared_buffers太大后,维护脏页列表和缓冲查找结构本身也会消耗大量CPU资源,在超高并发场景下可能出现得不偿失的情况。另外,PostgreSQL还要依赖操作系统页面缓存来补充,我会在后面讲effective_cache_size时详细说明这层配合关系。

设置方法很简单,在postgresql.conf中修改:

shared_buffers = 16GB

这个参数修改后需要重启数据库才能生效。注意,如果是Linux系统,还要检查内核参数kernel.shmmax和kernel.shmall是否允许分配这么大的共享内存段,不过现代PostgreSQL使用mmap分配共享内存,这个限制已经不像以前那么严格了,但如果系统日志中报出共享内存分配失败的记录,还是要回头检查这两个内核参数。

2.2 work_mem:查询操作的内存上限

work_mem是排序、哈希连接、聚合等操作可以使用的私有内存上限。它和shared_buffers完全不同——shared_buffers是所有会话共享的,work_mem是每个会话每个操作独立分配的。这就带来了一个很有意思的权衡:这个值设小了,排序操作会落到磁盘临时文件,性能急剧下降;设太大了,如果有几十个并发会话同时做排序操作,内存就可能被一下子吃光。

默认值是4MB,对于生产环境来说偏小。一个常见经验是把work_mem设为16MB到64MB之间,具体要看业务特征。怎么判断当前值够不够?执行下面的查询:

SELECT datname, temp_files, temp_bytes FROM pg_stat_database;

如果temp_files字段很大,说明排序或哈希操作频繁溢出到磁盘了,这时候就需要调大work_mem。反过来,如果你观察到系统交换空间(swap)用量持续上升,说明work_mem可能已经超配了。

关键点:work_mem是乘数效应。一条SQL里可能同时有多个排序和哈希操作,每个操作都会申请独立的work_mem。如果系统配置了100个并发连接,work_mem设为64MB,理论上最坏情况下内存占用就能到6.4GB。所以设定时不能只看单条SQL的舒适度,还要考虑并发度,这也是为什么生产环境一定要配合连接池使用的原因。

2.3 maintenance_work_mem:后台维护操作的专属内存

这个参数专门控制VACUUM、CREATE INDEX、ALTER TABLE ADD FOREIGN KEY等维护操作的内存上限。默认值是64MB,在业务表数据量大、索引复杂的情况下,这个值明显不够用。

我个人的经验是maintenance_work_mem可以大方一点,直接设为1GB到2GB。因为维护操作一般不是高频操作,而且它超过阈值后影响的是维护任务本身的磁盘I/O,不会像work_mem那样因并发而爆炸。特别是在重建大型索引的时候,我用默认64MB试过,几百万行的表建索引能等到怀疑人生,改成2GB之后速度快了数倍。

补充一个细节:autovacuum worker进程默认使用的内存是autovacuum_work_mem,如果没有单独设置,它会沿用maintenance_work_mem的值。如果系统中有大量更新删除操作,建议单独设置autovacuum_work_mem,避免VACUUM任务把maintenance_work_mem吃满,影响手动索引重建等操作的性能。

2.4 effective_cache_size:告诉优化器系统有多少缓存

这个参数很有意思——它不实际分配任何内存,而是向PostgreSQL的查询规划器提供“操作系统文件缓存大概有多大”的参考信息。规划器会根据这个数值判断是走索引扫描更划算,还是走顺序扫描更划算。

默认值是4GB,对于现代服务器来说这个值严重偏小。推荐设置为物理内存的50%到75%。继续用shared_buffers的表格举例:如果物理内存是32GB,shared_buffers设为8GB,那么effective_cache_size可以设为20GB左右。这个值加的是操作系统页面缓存的预估量,不是共享缓冲区。

为什么这个值只影响执行计划的选择?因为PostgreSQL的规划器在计算扫描路径代价时,会假设可以从系统缓存中免费读取一定量的数据。effective_cache_size越大,规划器越倾向于认为“数据已经在缓存里了”,从而更青睐索引扫描;越小,则越倾向顺序扫描。设置不当的典型症状是:某个查询明明用索引更快,但执行计划却选择了全表扫描,往往就是因为effective_cache_size设置得太小了。

3. 查询规划器参数:让执行计划更聪明

这部分参数经常被忽视,但它们对查询性能的影响往往比内存参数更直接。规划器用代价模型来比较不同执行路径的开销,而代价模型里两个最重要的基础参数就是seq_page_cost和random_page_cost。

3.1 seq_page_cost与random_page_cost:顺序扫描和随机扫描的代价

默认情况下seq_page_cost是1.0,random_page_cost是4.0。这组数字的含义是:PostgreSQL认为随机读一个页面的开销是顺序读一个页面的4倍。这个假设来自机械硬盘时代——磁头寻道确实非常昂贵。但在SSD时代,随机读和顺序读的差距已经大幅缩小,4.0这个比例就过于保守了,导致规划器经常低估顺序扫描的代价,高估索引扫描的代价。

对于SSD存储,我建议将random_page_cost调整为1.1到1.5之间。已经用了NVMe固态硬盘,可以试试1.1,这个值会让规划器更积极地选择索引扫描,在很多随机点查场景下能明显降低查询延迟。

如何验证调整效果?拿一个实际查询测试:

EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM orders WHERE user_id = 12345 AND created_at > now() - interval '7 days';

先看调整前的执行计划,再调整参数后对比。如果调整前是Seq Scan,调整后变成了Index Scan或Bitmap Index Scan,并且查询时间显著下降,那说明之前的代价估算确实有问题。

3.2 调整代价参数时要注意的坑

把random_page_cost调低并不是万能的。如果数据库是传统的机械硬盘,或者存储层存在大量随机读写性能差的网络存储,强行把这个参数调低,可能导致规划器盲目选择索引扫描,反而让查询更慢。这种情况下,执行计划会频繁出现Bitmap Heap Scan,但Block Hit Ratio却偏低,说明索引把一个本来可以顺序扫描的查询变成了大量随机小I/O。

另外,不要单独调整random_page_cost而忽略effective_cache_size。这两个参数是配合使用的:effective_cache_size决定“多少数据可以当作已经缓存在内存中”,random_page_cost决定“那些没在缓存中的数据,随机读取的成本有多高”。只调其中一个,执行计划往往会在两种极端之间来回横跳。我的做法是先设置好effective_cache_size,再用pgbench或真实业务SQL测试,逐步微调random_page_cost。最后固定参数后,在同一台机器上跑全量回归,确保没有查询劣化。

3.3 关于并行查询的几个参数

PostgreSQL的并行查询能力从9.6开始逐渐成熟,到了15版本已经相当可靠。并行查询相关的核心参数有:

参数默认值生产建议说明
max_parallel_workers_per_gather22~4单个查询最多使用的并行worker数
max_parallel_workers84~16整个实例的并行worker上限
max_parallel_maintenance_workers24~8维护操作(如CREATE INDEX)的并行度
parallel_setup_cost1000保持默认或调低启动并行worker的代价,调低后小表也倾向走并行
parallel_tuple_cost0.1保持默认每个tuple在并行worker间传递的代价

我踩过的坑:在一台只有4核的机器上把max_parallel_workers_per_gather设置成了8,结果一个复杂查询直接创建了超过CPU核数的并行进程,反而因为上下文切换开销导致查询变慢。后来改成2,配合正确的random_page_cost,查询性能反而提升了30%以上。并行度真不是越高越好,它要和你机器的物理核数匹配。一个简单的原则:max_parallel_workers_per_gather不要超过CPU核心数的一半。

4. 并发连接与资源管理:撑高吞吐但不压垮系统

连接管理是最容易被低估的生产环境问题。默认max_connections是100,很多人觉得不够用,直接改成1000甚至更多。但PostgreSQL是进程模型——每个连接都会对应一个独立的操作系统进程,而不是像MySQL那样是线程模型。1000个连接就意味着1000个进程,光是上下文切换就能把CPU资源烧光。

4.1 max_connections:不是越大越好

一句话总结我的经验:max_connections设得越大的系统,通常性能越差。因为每个PostgreSQL连接进程都要分配一定的内存,还要参与调度。当活跃连接数远超过CPU核心数时,大部分时间都浪费在进程切换上了。

一个健康的配置思路是:max_connections设置成“业务应用实际并发连接数上限”的2到3倍即可。绝大多数中小型业务系统,应用层连接池的并发连接数保持在20~50之间就足够了,所以max_connections设置在200到300之间是比较理想的范围。

如果你真的需要支持成千上万的客户端连接,正确做法不是疯狂调大max_connections,而是在应用层和数据库之间加一层连接池中间件(如PgBouncer或Pgpool-II)。这既避免了进程数爆炸,又能更高效地复用会话连接。我在生产环境中的推荐架构是:应用连接PgBouncer,PgBouncer再维护与PostgreSQL之间的一小撮稳定连接,比如20~40个。这样数据库侧的压力完全可控。

4.2 连接数耗尽的表现与快速定位

当max_connections被耗尽时,新连接会报出非常经典的错误:FATAL: sorry, too many clients already。这时候很多人第一反应是重启数据库,但重启只能暂时清空连接,上线几分钟后又会满。我当时排查这个问题时,首先用的是这条查询:

SELECT state, count(*) FROM pg_stat_activity GROUP BY state;

如果看到大量state为idle的连接,说明应用层连接池没有正确回收连接。再看看wait_event为ClientRead的连接有多少,这些通常都是空闲连接。另外,state为active的连接如果长时间不结束,就要考虑是否有慢查询拖住了连接。把慢SQL找出来优化掉,比单纯调大max_connections有效得多。

4.3 每个连接的内存预算

理解每个连接的内存预算,能帮你更科学地决定max_connections。公式大致是:每个连接的基础开销大约2MB,再加上work_mem的潜在占用。假设work_mem是16MB,连接数为300,最坏情况下仅排序操作就能消耗约4.8GB内存。如果work_mem是64MB,这个数字会变成19.2GB。所以max_connections和work_mem必须联动考虑,不能一个一个孤立设置。

建议用这个顺序做规划:先确定业务需要的并发连接数,再计算每个连接可能占用的最大内存,最后看物理内存是否扛得住。如果扛不住,要么减少连接数,要么降低work_mem,要么加内存。我在项目中专门做过一次压测:把连接从200提升到500,work_mem保持32MB,内存从16GB一路涨到40GB左右,但TPS并没有随之提升,反而因为CPU调度开销掉了将近15%。从那以后,我对“多开连接”这件事就谨慎了很多。

5. WAL与检查点:数据安全与性能的平衡术

WAL(Write-Ahead Logging,预写日志)是PostgreSQL保证数据一致性的核心机制。每次事务提交时,数据修改会先写入WAL日志,而不是直接写数据文件。WAL相关的参数调优,直接影响两个维度:崩溃恢复时的数据安全程度,以及写入密集型场景下的性能表现。

5.1 wal_level与synchronous_commit:安全级别怎么定

wal_level默认是replica,这个值适合绝大多数生产场景,它支持WAL归档和流复制。如果业务用不到这些高级特性,可以改成minimal减少一些日志量,但一旦后面要搭建从库或做时间点恢复,就需要修改配置并重启。我的建议是生产环境直接保持replica,nginx for future功能扩展,日志量多出的那一部分完全可以接受。

synchronous_commit是另一个关键的安全参数,它决定事务提交时是否需要等待WAL刷盘完成。默认是on,即每个事务提交都要等待WAL fsync,安全但慢。如果业务对数据安全性要求极高,可以设置成remote_apply以保证备库也应用了WAL后才返回到客户端,但那样主库性能会被拖累得非常明显。如果业务允许极短时间的数据丢失(例如某些日志系统、非核心告警记录),可以设置成off,性能提升非常可观——在写入密集场景下,TPS能提升1.5到2倍。

我的建议:核心交易系统绝对不要动synchronous_commit,保持on;但对于一些可以容忍丢失最近几秒数据的旁路系统,off是合理选择。不要一刀切。

5.2 max_wal_size与checkpoint_completion_target:检查点的节奏控制

PostgreSQL的检查点(checkpoint)机制是把shared_buffers里的脏页刷到磁盘的过程。检查点过于频繁,或者每次检查点要刷大量脏页,都会导致I/O抖动。max_wal_size就是控制两次检查点之间允许累积的WAL数据量上限。

默认max_wal_size是1GB,这在生产环境中通常会导致检查点过于频繁。我推荐的标准做法是把max_wal_size设置为3GB到10GB之间,具体要看写入量和可接受的恢复时间。检查点越不频繁,数据库运行期间的I/O越平稳,但崩溃恢复时需要回放的WAL就越多,恢复时间越长。

checkpoint_completion_target默认是0.5,意思是检查点在生成到一半WAL量的时候就“分散”完成刷脏。推荐改为0.9,把刷脏过程延伸到下一个检查点到来前尽量长的时间段,避免刷脏集中在同一时刻造成I/O尖峰。这两个参数配合起来,一个控制检查点的触发频率,一个控制刷脏的分散程度,共同压低“周期性I/O毛刺”。

这是我在生产环境遇到过的真实案例:某系统每两个小时就会出现一次数据库I/O延迟陡增,监控图显示像是定期发作。后来发现就是检查点导致的——max_wal_size太小,检查点频繁触发,checkpoint_completion_target又是默认的0.5,脏页全堆在检查点后半段突然刷盘。调整成max_wal_size=8GB和checkpoint_completion_target=0.9之后,I/O曲线变得非常平顺,再也没看到那种锯齿状尖峰。

5.3 WAL日志写盘相关的目录与I/O能力

如果业务是写入密集型,除了参数配置,WAL所在的目录也要单独考虑。PostgreSQL通过wal_level=replica配合归档模式时,WAL文件会不断产生并归档,如果与数据文件放在同一块磁盘上,写入I/O会相互竞争。

生产环境的建议是:为pg_wal目录挂载独立的高速存储(比如单独的SSD卷),并确保WAL磁盘的fsync性能过硬。调优时可以使用pg_test_fsync工具测试当前文件系统的fsync性能,选择表现最好的配置。实际部署中,把WAL目录分离出来之后,我见过不少系统的写入延迟直接下降了一半以上。

6. 日志与监控参数:生产环境排障的必修课

很多生产事故的排查之所以耗时很长,不是因为没有监控,而是PostgreSQL的日志配置太“勤俭”,落下来的有效信息太少。默认配置下,慢查询日志是关闭的,连接日志也是关闭的,真出问题了只能干瞪眼。

6.1 慢查询日志参数

慢查询日志是排查性能问题最直接的入口。我推荐的组合是:

logging_collector = on log_destination = 'csvlog' log_directory = 'log' log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log' log_statement = 'none' log_min_duration_statement = 1000 log_line_prefix = '%t [%p]: [%l-1] user=%u,db=%d,app=%a,client=%h '

log_min_duration_statement=1000表示只记录执行时间超过1000毫秒的SQL。初次上线时可以先设置为500ms,等系统运行稳定后再调到1000ms甚至2000ms。反过来,如果日志量太大不好分析,可以配合log_statement='none'只记录慢查询,避免把所有SQL都打进去。

一个我常用的调优小技巧:先在低峰期把log_min_duration_statement设为0,记录全量SQL半小时,然后按执行时间从大到小排序,找出最耗时的Top 20,有针对性地优化这些语句之后,再恢复成慢查询日志模式。这种方式相当于给数据库做一次“SQL体检”,对摸清业务负载特征特别有效。

6.2 追踪运行中的问题:pg_stat_statements

如果说慢查询日志是事后排查,pg_stat_statements就是事中监控的神器。它是一个扩展,需要在postgresql.conf中设置shared_preload_libraries='pg_stat_statements'然后重启数据库,再执行CREATE EXTENSION pg_stat_statements。

设置完成后,它会自动收集所有SQL语句的执行次数、总耗时、平均耗时、缓冲命中等信息,是找TOP SQL最快的路径。查询方式:

SELECT query, calls, total_exec_time, mean_exec_time, rows FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 10;

uploads里我把pg_stat_statements直接作为生产环境标配。没有它,做SQL调优就像闭着眼睛开车。还有一点要提醒:pg_stat_statements会占用少量内存来保存SQL文本和统计信息,如果SQL种类非常多,要检查它的内存占用,必要时调整pg_stat_statements.max,默认是5000,一般业务5000条足够,复杂业务可以调到10000。

6.3 自动清理参数:防止数据库悄悄“发胖”

autovacuum是PostgreSQL自动回收死行和更新统计信息的后台任务。默认autovacuum是开启的,但生产环境中如果表非常大,或者更新删除频率很高,默认的触发阈值可能不够。相关参数包括:

参数默认值生产建议说明
autovacuum_max_workers35~10并发清理worker数
autovacuum_naptime6030~60两次清理检查间隔(秒)
autovacuum_vacuum_scale_factor0.20.05~0.1表大小达到多少比例时触发清理
autovacuum_vacuum_threshold5050最小触发行数
autovacuum_analyze_scale_factor0.10.02~0.05统计信息更新触发比例

重点是scale_factor。对于一个100GB的大表,默认scale_factor是0.2,意味着要积累20GB的死行才会触发一次VACUUM。这段时间内表会迅速膨胀,查询性能下降非常明显。对于大表,建议把scale_factor调低,或者干脆在业务表上单独设置:

ALTER TABLE big_table SET (autovacuum_vacuum_scale_factor = 0.02);

这样设置后,VACUUM会更频繁但更轻量,避免一次清理扛太多死行。

6.4 日志里最常见的几种警报

有了合理配置的日志后,还需要知道什么是严重问题。生产环境最常见的日志警报有:

  • checkpoint not completing:检查点还没刷完,下一个就又触发了,说明max_wal_size偏小或刷盘I/O太慢
  • worker process ... did not exit normally:后端进程异常退出,要关注是否有内存不足或崩溃
  • long lock wait:长事务锁等待,通常意味着业务代码或死锁问题
  • could not stat file ... Permission denied:权限问题导致的WAL归档失败

这些警报如果日志里频繁出现,说明系统已经在亚健康状态了,需要立即处理。我以前有一个习惯就是每天上班第一件事,先登录服务器grep一下昨天日志里有没有以上模式。虽然现在很多公司都会上监控告警平台,但在数据和告警之间有延迟时,主动盯日志仍然能提前发现很多隐患。

7. 实操:从监控数据反推参数配置

调参不是拍脑袋。下面展示一套我从零开始为生产实例做配置优化的完整路径,供大家直接参考。

7.1 第一步:摸底现状

先收集几个基础信息:物理内存大小、CPU核数、磁盘类型(HDD还是SSD)、当前业务峰值并发数、读写比。然后查看当前配置和运行状态:

postgres=# SELECT name, setting, unit FROM pg_settings WHERE name IN ('shared_buffers','work_mem','max_connections');

同时跑一下pgbench做一次粗略的基准测试,确认当前配置的基准TPS和延迟。这步很有必要,否则光凭感觉调,之后也不知道究竟有没有变好。

7.2 第二步:按模板初始化

在摸底基础上,套一个经过验证的初始模板。就拿一台32GB内存、8核CPU、SSD磁盘的服务器举例:

shared_buffers = 8GB effective_cache_size = 24GB work_mem = 32MB maintenance_work_mem = 1GB max_connections = 200 max_parallel_workers_per_gather = 4 max_parallel_workers = 8 random_page_cost = 1.1 seq_page_cost = 1.0 max_wal_size = 8GB min_wal_size = 2GB checkpoint_completion_target = 0.9 synchronous_commit = on logging_collector = on log_min_duration_statement = 1000 autovacuum_max_workers = 6 autovacuum_vacuum_scale_factor = 0.05 shared_preload_libraries = 'pg_stat_statements'

注意work_mem这里我选了32MB而不是16MB,原因是在这个配置下,连接数200、最坏情况内存占用约6.4GB,加上shared_buffers和其他进程,32GB内存还是能稳稳扛住的。如果内存只有16GB,同样的模板就要把work_mem下调到16MB、max_connections降到100。

7.3 第三步:动态调整与验证

postgresql.conf中的参数分两类:一类是不需要重启的,用SELECT pg_reload_conf()即可生效;另一类需要重启。内存参数(shared_buffers)、max_connections、wal_level这类需要重启。work_mem、effective_cache_size、random_page_cost、log_min_duration_statement这些可以动态调整。

修改完需要重启的参数后,用pg_settings视图确认:

SELECT name, setting, pending_restart FROM pg_settings WHERE name IN ('shared_buffers','max_connections','wal_level');

如果pending_restart为t,说明参数还没有真正生效。重启后跑一遍业务压测,对比调整前后的pgbench数据。我一般盯三个指标:TPS、P95延迟、临时文件大小。TPS上去了,P95降下来了,临时文件几乎没有了,说明这次调参是成功的。

7.4 第四步:持续观察与微调

参数设置完不代表工作结束。我建议接下来两周内每天检查这几个视图:

  • pg_stat_database中的temp_files、temp_bytes——判断work_mem是否够用
  • pg_stat_user_tables中每个表的n_dead_tup、last_vacuum——判断autovacuum是否有遗漏
  • pg_stat_activity中的长事务和锁等待情况——判断是否出现连接或锁的瓶颈

有时候系统性能问题不是参数不对,而是分析思维不到位。比如work_mem已经调到128MB了,临时文件还是很多。再仔细查发现,是某一条SQL用了一个极不合理的联表条件,导致每次要排序几GB的数据。这种时候正确的做法是把SQL拆开优化或者加索引,而不是继续无脑调大work_mem。记住,参数只是将系统资源转化为性能的“调度器”,SQL本身的效率才是决定性能的上限。

8. 常见问题与排查技巧实录

整理了几个生产环境中大家最常踩的坑和排查心得,直接按问题给方案。

8.1 参数改了不生效

这是最高频的问题。改完postgresql.conf后,忘了执行SELECT pg_reload_conf(),或者执行了但忘了部分参数需要重启。排查思路很简单:查询pg_settings中pending_restart字段,值为true就是没重启。还有一个容易忽略的点:有些系统配置了多个配置文件片段,需要在postgresql.conf中用include指令引入。检查pg_settings里的source字段就能确定当前值到底来自哪个文件。

8.2 调整后性能反而更差

大概率是参数与硬件不匹配。最常见的情况是把random_page_cost调得太低,而存储恰好在随机I/O上有短板;或者是max_parallel_workers_per_gather调得过高,并行开销压过了收益。回退方式:把有疑问的参数改回原值,逐个变量验证。另一个原因是只调了内存参数但忽略了检查点参数,导致内存大大增加后,检查点刷脏量也成倍上涨,I/O毛刺更严重。所以内存参数和检查点参数一定要配套调整。

8.3 连接数耗尽但状态奇怪

如果连接数耗尽了,但pg_stat_activity里active连接并不多,那很可能是idle in transaction状态的事务占用了连接。出现这种情况通常是因为应用代码里开了事务但忘了提交或回滚。这时可以查找:

SELECT pid, state, duration, query FROM pg_stat_activity WHERE state = 'idle in transaction';

对发现的进程做定格分析,然后回头修应用代码。从这个角度看,连接耗尽未必是数据库配置问题,更可能是应用层事务管理问题,杀数据库连接只是治标不治本。

8.4 重启数据库失败

修改shared_buffers或max_connections后重启失败,通常有两个原因:一是权限问题,比如共享内存设置超限;二是postgresql.conf语法错误。解决问题的第一步是查看数据库日志文件。如果日志没输出有效信息,可以尝试用指定配置文件启动:

/usr/lib/postgresql/15/bin/postgres -D /etc/postgresql/15/main --config-file=/path/to/custom.conf

如果启动时报错,会直接把错误打到终端上,比翻日志快多了。另外,强烈建议每次改配置文件之前先备份,改完后用pg_ctl reload或restart测试,不要在满是业务的时段直接重启。

8.5 有哪些参数尽量不要动

最后总结几个我不建议轻易改动的参数:

  • fsync:默认on,强行关闭虽然能提升写入性能,但一旦操作系统或数据库崩溃,数据文件可能损坏到无法恢复的程度
  • full_page_writes:默认on,改动后崩溃恢复完整性没有保证
  • wal_sync_method:在大多数文件系统上默认就是最优选择,手动改了可能反而变慢
  • commit_delay和commit_siblings:这两个参数配合synchronous_commit使用,调不好会显著增加事务提交延迟,收益却很小

调优的边界在于:优先保证数据安全和崩溃恢复能力,在此基础上再追求性能上限。为了那点性能提升去牺牲数据安全,绝对是得不偿失的。

9. 根据个人经验再补几句大实话

从10年前的PostgreSQL 9.x时代到现在的16、17版本,我看过太多团队把时间花在“调参数”上,却始终没有把基础配置和环境摸清。其实PostgreSQL官方文档和EXPLAIN输出的信息量非常大,绝大多数性能问题都能通过日志和统计信息定位出方向,真正的瓶颈往往不是参数没调好,而是SQL写得不好、索引没建对、数据模型设计有问题。

参数调整这件事本身需要建立在一个基本认知上:每台服务器、每个业务场景的最优配置都不一样,照抄任何现成的配置模板都只是起点,不是终点。我建议拿到新环境后,按照文章里的方法做一轮摸底、一轮设置、两到三周的持续观察,才能逐步逼近最适合当前业务的参数组合。

最后分享一个小技巧:每完成一次重要调优,把改动前后的参数快照、性能基线、业务现象都记录下来,归档在项目文档里。下一次遇到类似问题时,这些记录就是最宝贵的参考——比任何网上的默认配置模板都更贴近你的实际场景。

筛选

仅为所见内容进行改写。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询