应用突然大面积报错,日志里一行 FATAL: sorry, too many clients already。这时候数据库还活着,只是不再接受新连接了。先别急着改 max_connections 重启——那样多半几小时后又会满。下面按「先止血、再定位、最后根治」的顺序来。
三分钟应急:把连接腾出来
PostgreSQL 默认给超级用户留了 3 个连接槽(superuser_reserved_connections),所以用 postgres 账号一般还能进去:
psql -U postgres -c "SELECT count(*), state FROM pg_stat_activity GROUP BY state;"
大概率会看到一堆 idle 或 idle in transaction。后者最危险,它不仅占连接,还卡住 vacuum。先把闲置超过 10 分钟的事务砍掉:
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE state = 'idle in transaction'
AND state_change < now() - interval '10 minutes'
AND pid <> pg_backend_pid();
业务通常能立刻恢复。但这只是止血。
看清楚连接是被谁占的
| state | 说明 | 怎么处理 |
|---|---|---|
| active | 正在执行 SQL | 看 query 字段,慢 SQL 优先优化 |
| idle | 连着但没干活 | 连接池配置过大,调小 max pool size |
| idle in transaction | 开了事务忘了提交 | 应用代码漏 commit/rollback,必须改 |
| idle in transaction (aborted) | 事务已报错未回滚 | 异常处理里补 rollback |
想知道是哪个服务在占,按客户端地址和应用名分组:
SELECT client_addr, application_name, count(*)
FROM pg_stat_activity
GROUP BY 1,2 ORDER BY 3 DESC;
我见过最典型的一次,是一个定时脚本每分钟建连接不关,跑了两天把 200 个槽吃光。
max_connections 到底该设多大
很多人第一反应是把 100 改成 1000。PostgreSQL 是进程模型,每个连接一个后端进程,连接数上去内存和上下文切换都会涨。经验值是按 CPU 核数和可用内存算,别盲目堆。
改配置后需要重启才生效:
ALTER SYSTEM SET max_connections = 300;
-- 重启服务
sudo systemctl restart postgresql
顺带把这两项也设上,能自动清掉忘记提交的事务:
ALTER SYSTEM SET idle_in_transaction_session_timeout = '5min';
ALTER SYSTEM SET statement_timeout = '30s';
SELECT pg_reload_conf();
根治靠连接池,不靠调大参数
业务侧有多少个实例、每个实例连接池上限多少,乘起来就是数据库要扛的连接数。四个应用实例各配 50,就是 200 个常驻连接,而它们大部分时间是 idle。正确做法是在数据库前面放 PgBouncer,用 transaction 模式复用:
[databases]
mydb = host=127.0.0.1 port=5432 dbname=mydb
[pgbouncer]
pool_mode = transaction
max_client_conn = 1000
default_pool_size = 25
前端可以挂 1000 个客户端连接,真正落到 PostgreSQL 的只有 25 个。应用侧连接池同时调小到 10 以内,两边配合才有效果。注意 transaction 模式下不能用会话级特性,比如 SET 变量和预备语句要按文档处理。
常见问题
问:pg_terminate_backend 会不会丢数据?
答:正在执行的事务会回滚,未提交的数据丢失,已提交的不受影响。所以优先杀 idle in transaction,别一上来就把 active 全清了。
问:max_connections 改了没生效?
答:这个参数是 postmaster 级别的,pg_reload_conf() 不管用,必须重启实例。用 SHOW max_connections; 确认当前值。
问:连接池已经很小了,还是满?
答:多半有长事务在积压。查 SELECT pid, now()-xact_start AS dur, query FROM pg_stat_activity WHERE state <> 'idle' ORDER BY dur DESC LIMIT 10;,把跑最久的那几条 SQL 拿去优化。
你们线上 max_connections 和连接池是怎么配的?欢迎到 fenij.com 留言交流具体数值。