被许多调用方共享的、保持打开的一组连接。

Postgres 连接很贵,要复用与池化

温热的连接池能够减少反复建立 PostgreSQL 连接,但空闲连接在池里等待期间也可能消失。服务器结束会话、网络故障或中间设备的空闲策略,都可能让客户端仍握着一个已经不能使用的套接字。复用方案必须包含失效识别和替换;对象还在池里,只说明应用保存了它,不能证明下一条查询一定成功。 坏掉的样子 TCP 保活能够识别原本空闲连接上的部分故障,却不会替 SQL 设置执行截止时间,也不证明数据库能够完成某个具体请求,更不会处理结果未知的提交。客户端连接参数只作用于 TCP,使用 Unix 域套接字时相关选项会被忽略;服务器端保活设置控制的是另一端。具体操作系统是否支持某个选项,也会影响最终行为,不能只看参数字符串存在就宣称故障检测已经发生。 借出前健康检查,可以在把连接交给调用方之前识别已经失效的连接。Psycopg 默认不执行这种检查,但提供回调让应用选择启用。多一次探测意味着额外往返,也仍然无法阻止连接在探测成功后立刻出故障。因此,建立连接、等待租用和执行语句,都需要各自的预算与错误处理,不能用一个“保活开启”覆盖全部阶段。 快速验证 准备 Python 3.10 或更新版本,通过 TCP 连接使用默认零值语句超时的 PostgreSQL 18,并安装本次验证版本:python3 -m pip install "psycopg[binary]==3.3.5" "psycopg-pool==3.3.1"。把 PG_DSN 设置为可丢弃数据库,使用允许结束自己后台连接的角色。保存为 liveness-demo.py 后运行。程序先记录每个测试池刚创建的连接标识,然后只结束那条已经归还、处于空闲状态的连接: import os import psycopg from psycopg.errors import QueryCanceled from psycopg_pool import ConnectionPool dsn = os.environ['PG_DSN'] with psycopg.connect(dsn, autocommit=True) as admin: for checked in (False, True): with ConnectionPool( dsn, min_size=1, max_size=1, timeout=3, open=True, check=ConnectionPool.check_connection if checked else None, kwargs={ 'connect_timeout': 2, 'keepalives': 1, 'keepalives_idle': 30, 'keepalives_interval': 10, 'keepalives_count': 3, }, ) as pool: pool.wait(timeout=3) with pool.connection() as conn: assert conn.execute('SELECT inet_server_addr() IS NOT NULL').fetchone()[0] original_pid = conn.info.backend_pid parameters = conn.info.get_parameters() assert [parameters[k] for k in ( 'keepalives', 'keepalives_idle', 'keepalives_interval', 'keepalives_count' )] == ['1', '30', '10', '3'] # Terminate only the idle connection just created by this fixture. assert admin.execute('SELECT pg_terminate_backend(%s,2000)', (original_pid,)).fetchone()[0] if not checked: try: with pool.connection() as conn: conn.execute('SELECT 1') except psycopg.OperationalError as error: print('unchecked_first_use', error.sqlstate) else: raise AssertionError('terminated connection unexpectedly worked') with pool.connection() as conn: replacement_pid = conn.info.backend_pid assert replacement_pid != original_pid assert conn.execute('SELECT 1').fetchone()[0] == 1 print('checked', checked, 'replacement', True) try: with pool.connection() as conn: conn.execute("SET LOCAL statement_timeout='50ms'") conn.execute('SELECT pg_sleep(0.2)') except QueryCanceled as error: assert error.sqlstate == '57014' print('statement_timeout', error.sqlstate) else: raise AssertionError('long statement did not time out') with pool.connection() as conn: assert conn.info.backend_pid == replacement_pid assert conn.execute('SHOW statement_timeout').fetchone()[0] == '0' assert conn.execute('SELECT 1').fetchone()[0] == 1 print('healthy_after_rollback', True) 两种情况都配置了客户端保活参数,并核对有效值,不打印整个连接串。第一种没有借出前检查。空闲后端被服务器结束后,下次读取遭遇操作错误;本次这种明确的服务器关闭返回 57P01。池随后丢弃坏连接,下一次借用得到不同的后端标识,并成功返回一。 ...

八月 9, 2027 · 2 分钟 · XB Field Notes