ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

PostgreSQL连接从入门到排障:报错分析与连接池实践

PostgreSQL连接从入门到排障:报错分析与连接池实践 装好PostgreSQL之后第一件让人血压升高的事就是连不上。我这些年帮人排过太多这种问题明明安装顺利、服务也在跑但客户端就是报错一会儿password authentication failed一会儿Connection refused一会儿no pg_hba.conf entry。很多人习惯把这件事叫postgresql链接我顺着这个说法讲实际说的就是Connection——客户端与数据库服务器建立会话的整个过程。这篇文章我从底层原理、客户端工具、程序驱动、报错排查到连接池把连接这件事完整拆一遍。适合刚装完PostgreSQL不知道怎么连的新手也适合被各种诡异报错折磨过、想系统搞懂排查逻辑的人。文章里的路径、命令都以Windows和Linux常见环境为例演示用的版本是PostgreSQL 15/16其他版本基本通用。1. 从装好了却连不上说起连接的两条通道与认证闸门1.1 PostgreSQL连接的本质是两段式很多人以为PostgreSQL连接就是我填个IP、端口、用户名、密码好像跟访问网站一个道理。实际上它比那多一道关卡而且这道关卡恰恰是绝大多数连接失败的根源。PostgreSQL的连接分成两个层面。第一层是TCP层面客户端要能到达服务器的5432端口默认端口这涉及监听地址、防火墙、安全组。第二层是认证层面即使TCP通了服务器还要根据pg_hba.conf文件里的规则决定允不允许这个来源IP用这种方式认证。打个比方TCP层面相当于你到了小区门口保安防火墙放你进了。认证层面相当于单元门禁——门禁系统里没录入你的指纹你照样刷不开。很多人遇到密码明明是对的但报错其实就是单元门禁规则写得不对或者你用错了几号楼的单元门禁。1.2 本地Socket与TCP/IP两条通道规则不同PostgreSQL支持两种连接通道Unix域套接字Unix socket和TCP/IP。在Linux/Unix系统上你用psql不加-h参数时默认走的是Unix socket加了-h 127.0.0.1强制走TCP。在Windows上则比较简单主要就是TCP/IP。这两条通道在pg_hba.conf里的规则是分开写的。我见过一个经典场景用户在服务器本机用psql -U postgres能连上但用Navicat从另一台电脑连就报no pg_hba.conf entry。原因就是pg_hba.conf里只配了local规则或127.0.0.1的规则没有配局域网或公网来源的规则。默认的pg_hba.conf长这样local all all peer host all all 127.0.0.1/32 scram-sha-256 host all all ::1/128 scram-sha-256注意第三行是IPv6的localhost地址::1。如果你在应用里填的是localhost某些系统上会优先解析到IPv6的::1结果认证规则匹配不上或者服务端只监听了IPv4导致连不上。这种坑在排错时特别容易绕晕。1.3 服务端监听参数listen_addressespostgresql.conf里有个参数叫listen_addresses默认值是localhost。这意味着默认情况下PostgreSQL只允许本机连接无论你防火墙怎么开都没用——服务器压根没在对外网卡上监听端口。要允许远程连接需要把它改成listen_addresses *或者指定具体IPlisten_addresses 192.168.1.100修改之后需要重启服务注意listen_addresses不是reload就能生效的必须重启postmaster进程。这是新手特别容易弄错的点——改了配置后执行SELECT pg_reload_conf()发现还是连不上就以为配置没生效其实只是这个参数需要重启而已。改完之后怎么验证监听起来了Linux上执行ss -tlnp | grep 5432Windows上执行netstat -ano | findstr 5432如果看到0.0.0.0:5432或:: :5432在LISTENING说明监听没问题了。如果只看到127.0.0.1:5432说明配置还没生效或者服务没重启。2. 客户端工具实操psql、Navicat、DataGrip的三种连接姿势2.1 psql排查连接问题时的第一工具psql是PostgreSQL自带的命令行客户端也是我强烈建议所有人最先学会用的工具。为什么因为它是和数据库服务器直接交互的最小客户端图形化工具出的各种古怪问题在psql面前会原形毕露。最基本的连接命令psql -h 127.0.0.1 -p 5432 -U postgres -d postgres-h是主机名-p是端口-U是用户名-d是要连接的数据库名。如果不写-d默认会尝试连接和你用户名同名的数据库——postgres用户对应postgres数据库所以通常没问题。连接时会提示输入密码。也可以用环境变量避免交互式输入# Windows PowerShell $env:PGPASSWORD你的密码 psql -h 127.0.0.1 -U postgres -d postgres # Linux PGPASSWORD你的密码 psql -h 127.0.0.1 -U postgres -d postgres连接成功后执行SELECT version();和SELECT current_database();能看到版本信息和当前数据库名。这一步确认无误至少说明TCP层和认证层都没问题。2.2 psql连接时常见的三个失误第一个失误-h写成了localhost导致走IPv6而连不上。优先用127.0.0.1排查阶段不要用localhost。第二个失误密码里带特殊字符粘贴到命令行里被shell解析了。比如密码里有$、、空格这些在PowerShell或Bash里要加引号或转义。更推荐的方式是写进.pgpass文件Windows上叫%APPDATA%\postgresql\pgpass.conf。第三个失误用psql连远程数据库时忘了指定-d。某些服务器上默认数据库不叫postgres如果postgres数据库被删了或者改名了-U postgres这个用户名对应的同名数据库可能不存在连接就会报database postgres does not exist。其实数据库服务器本身是通的只是默认库找不到而已。2.3 Navicat和DataGrip图形工具的配置细节Navicat连PostgreSQL相对简单填几个框就行主机、端口、初始数据库、用户名、密码。初始数据库这个字段值得说一句它的意思是连接建立后默认进入哪个数据库。如果留空Navicat有时会尝试连postgres库有时会根据用户名猜一个库名行为不够透明。我建议总是显式填postgres或你实际要用的库名。DataGrip则要复杂一点。第一次连接PostgreSQL时它会提示下载驱动如果网络环境不好比如从内网访问外网受限驱动下载会失败。这时候需要手动下载PostgreSQL JDBC驱动jar包然后到DataGrip的数据库驱动管理里指定jar包位置。无论用什么图形工具排查连接问题时我的判断顺序始终是先用psql在服务器本机连一次确认服务端正常再在客户端机器上用psql连一次确认网络链路通最后才轮到图形工具。跳过前面两步直接怀疑图形工具很容易浪费时间。3. 程序代码里的连接串JDBC与Python驱动逐参数拆解3.1 JDBC URL的结构与常用参数Java生态连接PostgreSQL基本都是用官方JDBC驱动org.postgresql:postgresql连接串格式如下jdbc:postgresql://192.168.1.100:5432/mydb?usermyuserpasswordmypasscurrentSchemamyschema这段URL拆开来看jdbc:postgresql://是固定前缀不可省略。192.168.1.100:5432是主机和端口。如果是本机可以写成localhost或127.0.0.1。/mydb是要连接的数据库名。问号后面是参数。user和password是最基本的。currentSchema指定默认的schema比如你有一堆表在myschema下而不在public下不指定的话SQL里写表名可能要带schema前缀。比较值得说的几个参数ApplicationNamemyapp这个参数看起来不起眼但强烈建议设置。它会让连接在pg_stat_activity视图里显示为myapp而不是客户端默认的名字。线上排障时一眼看出连接是哪路应用发起的省去大量猜测。connectTimeout10单位是秒控制TCP建连超时。不设的话可能卡在系统默认TCP超时上几十秒甚至更久应用层面很容易出现操作卡死的假象。socketTimeout控制一次SQL执行过程中的socket读超时。注意这个参数和connectTimeout不一样很多人混淆。设得太短慢查询容易误报超时。ssltrue要不要启用SSL加密连接。生产环境建议开启但要在服务器端配好证书。密码明文写在URL里有个现实问题连接串会被打进日志、出现在监控平台。我见过不止一次开发把密码提交到Git仓库的事故。更稳妥的做法是通过环境变量或配置中心把密码注入JDBC驱动也支持从pgpass文件读取密码。3.2 Pythonpsycopg2和SQLAlchemy的DSNPython生态里最常用的驱动是psycopg2连接方式有两种。第一种用关键字参数import psycopg2 conn psycopg2.connect( host192.168.1.100, port5432, dbnamemydb, usermyuser, passwordmypass, connect_timeout10, application_namemy_python_app )第二种用DSN字符串dsn postgresql://myuser:mypass192.168.1.100:5432/mydb conn psycopg2.connect(dsn)DSN的格式是postgresql://用户名:密码主机:端口/数据库名。这个格式在SQLAlchemy、psql命令、各种云服务商的连接指引里都能看到统一标准强烈建议花两分钟记住。如果用SQLAlchemy引擎创建语句是这样from sqlalchemy import create_engine engine create_engine( postgresqlpsycopg2://myuser:mypass192.168.1.100:5432/mydb, pool_size10, max_overflow5, pool_pre_pingTrue )pool_pre_pingTrue这个参数是我特别想推荐的。它在每次从连接池取出连接时先执行一次轻量查询默认是SELECT 1确保连接没有因为网络空闲、数据库重启等原因变成死连接。没有这个参数连接池里可能躺着几根早已失效的连接第一次查询就报connection has been closed排查起来能熬秃。3.3 连接串里指定search_path一个不起眼但能救命的参数PostgreSQL的schema概念比MySQL的database更复杂。同一个数据库里可以有好几个schema执行SQL时如果表名不带schema前缀靠的是search_path搜索路径决定先去哪个schema找表。连接串里可以带options参数来指定jdbc:postgresql://192.168.1.100:5432/mydb?usermyuserpasswordmypassoptions-csearch_path%3Dmyschema,publicPython DSN里可以写成dsn postgresql://myuser:mypass192.168.1.100:5432/mydb?options-csearch_path%3Dmyschema,public为什么说这个能救命因为很多公司的数据库并不把所有表放在public下开发本地测试库和生产库的search_path可能不一致。本地跑得好好的SQL连上生产库就报relation not found多半就是search_path的问题。显式在连接串里指定可以抹平环境差异。4. 连接报错排查实录从报错信息反推到根因的完整路径4.1 常见报错速查表先给一张我这些年最常遇到的报错对照表按出现频率排序报错信息根因解决方向FATAL: password authentication failed for user xxx密码错误或认证方式不匹配确认密码检查pg_hba.conf中对应的认证方法connection to server on socket /var/run/postgresql/.s.PGSQL.5432 failed: No such file or directory服务没启动或socket路径不对检查服务状态确认unix_socket_directoriescould not connect to server: Connection refusedTCP层不通服务没监听或端口被拒绝netstat看监听检查listen_addresses和防火墙FATAL: no pg_hba.conf entry for host 192.168.1.50, user myuser, database mydbpg_hba.conf里没有匹配来源IP的规则新增对应的host规则并reloadtimeout expired/connect timeout to server网络链路不通或端口被防火墙丢弃在客户端用telnet测试端口检查安全组FATAL: database xxx does not exist连接串指错了数据库名确认实际的数据库名The connection attempt failed: connection reset by peer中间设备拦截或服务端crash后立即复位检查服务日志看是否触发内存不足被OOM killer干掉4.2 一条完整的排查链路从应用报错追到安全组规则两个月前帮一个朋友排查过一个问题他的Spring Boot应用部署在云服务器A上PostgreSQL安装在另一台云服务器B上应用启动时报Connection refused但他在服务器B上本机用psql连数据库完全正常。具体排查过程是这样的。第一步确认PostgreSQL监听范围。在服务器B上执行ss -tlnp | grep 5432输出是127.0.0.1:5432问题找到了吗其实没完全找到。127.0.0.1说明确实没开对外监听但服务器B的postgresql.conf里listen_addresses已经写了*为什么只监听了127.0.0.1这个坑比较隐蔽操作系统层面有多个网卡时PostgreSQL会监听所有网卡地址而ss输出里通常每个地址一行。如果只看到127.0.0.1:5432说明进程可能没有读到最新配置。检查一下发现服务是在配置文件修改之前启动的一直没重启过。执行重启后ss输出变成了0.0.0.0:5432和:::5432。第二步从应用服务器测端口。在服务器A上执行telnet 服务器B的IP 5432如果telnet连不上可能是防火墙拦了。检查Linux本机防火墙iptables -L -n | grep 5432某些云服务商默认系统防火墙是放开的但安全组在外面拦了一层。这里查完发现系统防火墙是通的那就去云控制台看安全组规则——结果果然没放行5432端口。加上一条允许来源IP为服务器A的5432端口入站规则后应用就起来了。第三步如果端口通了还报认证错误再看pg_hba.conf。这个例子因为是最基础的网络不通但完整的排查链路应该包含这一步确认应用连接的用户和数据库在pg_hba.conf里有匹配的来源IP规则。这整套排查链路的核心逻辑是从近到远、从服务端到客户端。先证明数据库本身没问题再逐层验证监听、防火墙、安全组、认证配置。4.3 pg_hba.conf修改时最容易踩的坑pg_hba.conf这个文件我改过太多次了每次都有新教训。挑了三个高频坑说说。第一改完不reload。pg_hba.conf是可以通过SELECT pg_reload_conf();动态加载的不需要重启服务。但很多人不知道改了文件直接测试连接发现不生效就开始怀疑其他地方。改完之后一定要显式执行一次reload。第二规则顺序问题。pg_hba.conf是从上到下按顺序匹配的匹配到第一条就停止。我见过有人把host all all 0.0.0.0/0 reject写在最上面把允许规则写在下面结果所有人全被拒了。如果要用reject一定要放在明确允许的规则之后。第三认证方法写错。高版本PostgreSQL14及以上推荐用scram-sha-256但有些老客户端比如旧版JDBC驱动、旧版psycopg2不支持这个认证方式必须用md5。如果客户端报认证方法不支持要检查是不是驱动版本太老而不是急着把认证方式改成md5——改md5会降低安全性治标不治本。5. 连接池与连接生命周期高并发下不能忽视的持续性话题5.1 为什么PostgreSQL的连接比想象中重MySQL使用线程模型一个连接对应一个线程资源开销相对可控。PostgreSQL使用进程模型每个客户端连接对应一个服务端进程fork出来的backend process。进程之间的内存是独立的连接越多内存开销越大上下文切换成本也越高。PostgreSQL默认的max_connections是100实际安装包可能配得更高但这不意味着你就能同时撑100个应用连接。每个连接大约会占用几MB到几十MB内存还要考虑共享缓冲区的开销。我一般建议如果应用需要大量并发连接优先使用连接池。这就像饭馆PostgreSQL是后厨每个连接就是一口灶。灶很多但厨师CPU就那么多灶再多也炒不出更多菜反而占用厨房空间。连接池的意义就是让有限的灶被循环高效使用。5.2 HikariCP参数配置的经验值Java应用最常用的连接池是HikariCPSpring Boot默认集成。我的配置经验如下spring: datasource: hikari: # 连接池中最大连接数 maximum-pool-size: 20 # 最小空闲连接数 minimum-idle: 5 # 连接最大存活时间建议比数据库wait_timeout小 max-lifetime: 1800000 # 连接空闲超时时间 idle-timeout: 600000 # 获取连接的超时时间 connection-timeout: 30000 # 从池中取连接前执行一次校验查询 connection-test-query: SELECT 1其中maximum-pool-size怎么定比较合理没有公式能一步算准但有一个常用的估算思路峰值并发请求数 × 单请求平均耗时秒这个积除以应用实例数再留20%-30%余量。比如单实例峰值并发100个请求平均每个请求耗时0.2秒那理想的池大小是20左右。注意调大池大小不会让每个请求更快反而可能因为连接过多、服务端负载升高而变慢。max-lifetime这个参数值得单独说说。很多时候数据库服务端或中间网络设备会自动断开空闲连接客户端却不知道导致用的时候才发现连接已失效。max-lifetime设为略小于服务端连接超时的时间可以让连接池主动回收这些连接避免用脏连接。配合connection-test-query或其他驱动自带的ping机制基本能避免大半连接被远端关闭问题。5.3 应用层连接池解决不了的问题应用层连接池解决的是同一个应用实例内部复用连接的问题。如果部署了20个应用实例每个实例池大小20那数据库端就会有400个连接。PostgreSQL默认的max_connections很可能就不够用了。这种场景下有两个方向一是提高max_connections但要同步评估系统资源二是在数据库前面加一层服务端连接池比如PgBouncer。PgBouncer可以把多个客户端的连接复用到少量PostgreSQL后端连接上特别适合大量短连接请求的场景。不过引入PgBouncer会增加一层运维复杂度小规模应用或连接数没超过200的场景先不必上。还有一点容易被忽略连接池和数据库的max_connections要配套。建议把数据库的max_connections设置为所有应用连接池总和的上限再加20%左右的余量同时给超级用户预留几条专用连接PG默认会保留superuser_reserved_connections一般设3否则紧急排查时连管理连接都建不上。6. 几个实测后觉得特别值得分享的连接小细节6.1 用pgpass文件免去密码交互命令行和脚本里处理PostgreSQL连接密码最优雅的方式是.pgpass文件。Linux上放在~/.pgpassWindows上是%APPDATA%\postgresql\pgpass.conf内容格式是hostname:port:database:username:password比如192.168.1.100:5432:mydb:myuser:mypassword文件权限要注意Linux下必须是600chmod 600 ~/.pgpass设置好之后psql -h 192.168.1.100 -U myuser -d mydb就不会再弹密码交互了脚本里可以安全地使用。这个文件也支持JDBC驱动官方驱动会读取它一劳永逸。6.2 pg_stat_activity是连接排查的监控探头连接一旦建立你可以在数据库端实时看到它SELECT pid, usename, application_name, client_addr, client_port, state, query FROM pg_stat_activity;这个视图是连接故障排查的好帮手。state字段如果是active说明正在跑查询如果是idle说明连接空着但还占着一个进程如果一堆连接都是idle in transaction说明应用在事务里没提交也没回滚连接池会被这些僵尸事务占满新请求全都卡在等连接上。遇到连接数打满的问题先看这个视图比先改配置文件要快得多。6.3 连接超时参数防呆设计不能省不管用什么方式连接生产环境都要设置连接超时。Java就用connectTimeoutPythonpsycopg2用connect_timeoutpsql命令也有connect_timeout环境变量。设个10秒左右比较合适太长会导致应用线程被卡死太短又会误伤网络抖动。最后再说一个我自己的习惯每次改完pg_hba.conf或postgresql.conf都会在改动旁边加一行注释记录为什么改、改了什么、什么时候改的。配置文件里不写注释三个月后回头看基本就是天书重蹈覆辙的概率很大。数据库连接这个事说复杂也复杂说简单也简单——只要按服务监听→网络链路→认证规则→驱动配置这条线逐个验证90%的问题都能找到根因。剩下那10%多半要靠pg_stat_activity这个探头去发现那些藏在连接背后的事务问题。
返回列表