1. psycopg2 查询总超时从连接串到认证链路的排查思路psycopg2是 Python 生态里连接 PostgreSQL 最常用的驱动之一psycopg2.connect()加上cursor.fetchall()几乎是每个数据脚本的标配。但很多人会遇到一个很别扭的现象本地psql命令行查同一张表秒回换成 Python 脚本就频繁报connection timed out、could not connect to server或者查询跑到一半卡死连接池里的连接越攒越多最后直接耗尽。这个问题表面看是「数据库慢」实际上十有八九出在连接参数和认证链路上。先说清楚这篇适合谁如果你在用psycopg2或psycopg2-binary做数据查询、ETL、定时任务遇到超时、连接被拒、连接池打满并且已经确认 SQL 本身没问题那这篇就是给你写的。核心检索词就是 psycopg2 查询数据超时我会从连接串写法、认证配置、超时参数三个角度拆开讲最后给一套可复制的参数模板。为什么psycopg2比命令行更容易超时因为命令行工具通常读的是本机~/.pgpass或环境变量连接目标、端口、SSL 模式都是隐式确定的而psycopg2的连接参数是你在代码里显式拼出来的任何一个字段写错比如host写成了内网域名但 DNS 解析慢、sslmode没配对导致握手反复重试、connect_timeout没设导致默认无限等待都会表现为「查询超时」。更隐蔽的是连接池场景psycopg2.pool.SimpleConnectionPool在拿不到连接时会阻塞如果前面的连接因为认证失败没被正确释放池子很快就被占满后续请求全部排队直到超时。还有一个常见误区是把「查询超时」和「连接超时」混为一谈。psycopg2里connect_timeout管的是建立 TCP 连接和认证阶段的等待时间而语句执行阶段的超时要用options-c statement_timeout5000这类服务端参数或者用cursor.execute()配合SET LOCAL statement_timeout。如果你只设了前者SQL 本身跑很久照样会卡住看起来还是「查询超时」。所以排查第一步是先分清卡在连接阶段还是执行阶段在connect()前后打时间戳如果connect()本身就花了几十秒那问题在连接串和认证如果connect()很快但fetchall()慢那要看语句超时和网络往返。我试过把同一套查询逻辑从直连数据库改成走统一通道超时问题基本消失原因后面会讲。这里先建立一个判断框架连接阶段超时 → 查 host/port/sslmode/connect_timeout认证阶段失败 → 查用户名密码、认证方式、Key 是否有效执行阶段超时 → 查 statement_timeout、索引、返回数据量。把这三层分开你就不会在一堆参数里瞎改。2. TaoToken 统一通道前置准备Key、Base URL 与模型 ID在动手改psycopg2参数之前先把统一通道这边的三件套准备好否则后面配置填不进去。所谓三件套就是 Base URL、API Key、Model ID这三个东西在任何统一接入场景里都是必须显式给出的缺一个都会导致认证失败或路由不到目标。Base URL 用https://taotoken.net/api注意这个地址不带任何查询参数直接作为请求根路径。API Key 需要到控制台里创建入口是 API Keys 页面创建后复制那串以sk-开头的字符串只显示一次丢了只能重建。Model ID 则取决于你要调用的具体能力比如对话类、代码类各有对应的标识填错会报模型不存在。这里要强调一个安全习惯Key 不要硬编码进.py文件然后提交到 Git。正确做法是放进环境变量或.env文件代码里用os.environ.get()读取。我见过太多人把 Key 写死在脚本里结果仓库一公开 Key 就泄露了。统一通道的 Key 通常有额度或权限范围泄露后别人可以拿你的额度跑请求所以这一步别偷懒。如果你用的是 Claude Code 这类编码工具配置方式又不一样它读的是 settings 文件而不是环境变量。这种情况下三件套要写进对应的 JSON 配置里Base URL、Key、Model ID 一个都不能少。同理Cline 走 MCP 配置、Codex 走auth.json都是同样的三件套逻辑只是载体不同。记住这个原则不管什么工具先确认它从哪里读 Base URL、从哪里读 Key、从哪里读 Model ID把这三个位置填对接入就成功了一大半。准备阶段还有一件事确认你的网络能正常访问taotoken.net。如果连控制台都打不开那后面所有配置都无从谈起。可以用curl -I https://taotoken.net/api看返回状态码能返回 401 或 200 都说明网络通401 只是说明没带 Key属于正常现象。这一步能帮你快速排除网络层问题避免把网络不通误判成配置错误。3. 可复制的 psycopg2 连接参数模板与统一 Key 配置这一节是重点直接给可复制的代码和配置。先说psycopg2的连接参数模板。核心思路是把连接阶段的所有超时和认证参数显式写全不留默认值。import os import psycopg2 from psycopg2 import pool from psycopg2.extras import RealDictCursor DB_CONFIG { host: os.environ.get(PG_HOST, 127.0.0.1), port: int(os.environ.get(PG_PORT, 5432)), dbname: os.environ.get(PG_DB, postgres), user: os.environ.get(PG_USER, postgres), password: os.environ.get(PG_PASSWORD, ), connect_timeout: 5, sslmode: prefer, application_name: psycopg2_query_job, options: -c statement_timeout8000 -c lock_timeout3000, } def get_conn(): return psycopg2.connect(**DB_CONFIG) def query_rows(sql, paramsNone): conn get_conn() try: with conn.cursor(cursor_factoryRealDictCursor) as cur: cur.execute(sql, params or ()) return cur.fetchall() finally: conn.close()这段模板里几个参数值得单独说。connect_timeout5表示建立连接最多等 5 秒超过就抛异常避免无限挂起。sslmodeprefer表示优先 SSL连不上再降级如果你明确要求加密就改成require。options里的statement_timeout8000是服务端语句超时8 秒没跑完就中断lock_timeout3000是等锁超时防止被别的长事务堵死。这三个超时配合起来基本能覆盖连接和执行两个阶段的卡死问题。如果你要走统一通道做认证和路由Key 的配置单独放一份不要和数据库密码混在一起。下面是一个.env风格的配置示例# 统一通道三件套 TAOTOKEN_BASE_URLhttps://taotoken.net/api TAOTOKEN_API_KEYsk-你的Key TAOTOKEN_MODEL_ID你的模型ID # 数据库连接 PG_HOST127.0.0.1 PG_PORT5432 PG_DBpostgres PG_USERpostgres PG_PASSWORD你的密码然后在代码里读取import os from dotenv import load_dotenv load_dotenv() BASE_URL os.environ[TAOTOKEN_BASE_URL] API_KEY os.environ[TAOTOKEN_API_KEY] MODEL_ID os.environ[TAOTOKEN_MODEL_ID]如果你用的是 Claude Code配置要写进 settings 文件格式是 JSON三件套字段名按工具要求来Base URL 填https://taotoken.net/apiKey 填你创建的sk-串Model ID 填对应标识。Cline 走 MCP 的话配置写在 MCP 的 JSON 里同样是这三个字段。Codex 的auth.json也是类似结构。不管哪种核心都是把 Base URL、Key、Model ID 填对别漏项。连接池场景再补一个模板用ThreadedConnectionPool并设置最大连接数避免池子被占满from psycopg2 import pool connection_pool pool.ThreadedConnectionPool( minconn1, maxconn10, **DB_CONFIG ) def query_with_pool(sql): conn connection_pool.getconn() try: with conn.cursor(cursor_factoryRealDictCursor) as cur: cur.execute(sql) return cur.fetchall() finally: connection_pool.putconn(conn)注意putconn一定要放在finally里否则一旦查询抛异常连接不会归还池子很快耗尽表现就是后续请求全部超时。这是连接池耗尽最常见的原因没有之一。4. 验证请求与成功结果超时复现与修复对比配置写完要验证不能凭感觉说「应该好了」。验证分两步先复现超时再确认修复。复现超时最简单的方法是故意把connect_timeout设成很小或者把 host 指向一个不可达地址看异常是不是按预期抛出。import time import psycopg2 bad_config { host: 10.255.255.1, port: 5432, dbname: postgres, user: postgres, password: x, connect_timeout: 3, } start time.time() try: psycopg2.connect(**bad_config) except psycopg2.OperationalError as e: print(连接失败耗时 %.2f 秒 % (time.time() - start)) print(错误信息:, str(e)[:120])跑这段正常会在 3 秒左右抛出connection timed out说明connect_timeout生效了。如果等了很久才报错说明参数没被识别检查是不是拼写错了或者被其他配置覆盖了。修复后的验证用真实查询import time from psycopg2.extras import RealDictCursor start time.time() conn psycopg2.connect(**DB_CONFIG) connect_cost time.time() - start start time.time() with conn.cursor(cursor_factoryRealDictCursor) as cur: cur.execute(SELECT id, name FROM users LIMIT 100) rows cur.fetchall() query_cost time.time() - start conn.close() print(连接耗时 %.3f 秒 % connect_cost) print(查询耗时 %.3f 秒 % query_cost) print(返回行数:, len(rows)) print(首行:, rows[0] if rows else None)成功的结果应该是连接耗时在几十毫秒到几百毫秒查询耗时取决于数据量但不会卡死返回行数和预期一致。如果连接耗时仍然很长检查 DNS 解析和网络链路如果查询耗时很长检查statement_timeout是否生效以及 SQL 是否有索引。关于fetchall()返回的数据结构这里顺带说一句默认返回的是 tuple 列表每个 tuple 对应一行字段顺序和 SELECT 一致。如果你想要字典形式用RealDictCursor返回的就是RealDictRow可以按字段名取值。很多人遍历时发现「每个对象都是一个新的列表或 tuple」其实就是默认游标的行为不是 bug。要改结构就换游标工厂别去改查询逻辑。验证通过后建议把连接耗时和查询耗时打到日志里长期观察。一旦某天耗时突然上涨你就能第一时间发现是网络问题还是数据量问题而不是等用户报障才去查。5. 本篇常见错误排查401、local proxy failed、reading choices、OAuth配置过程中最容易撞的几个报错这里逐个对照。401 UnauthorizedKey 没带、带错、或者过期。检查TAOTOKEN_API_KEY是否以sk-开头是否有多余空格是否在控制台被删除或重置。如果 Key 是对的还报 401检查请求头字段名是否正确有些工具要求Authorization: Bearer sk-xxx少个 Bearer 也会 401。local proxy failed这个通常出现在本地代理配置场景说明请求没走到目标地址就被本地代理拦了。检查环境变量里有没有HTTP_PROXY、HTTPS_PROXY指向一个不可用的地址有的话临时清掉再试。注意这里说的是本地环境变量层面的代理配置不是让你去搭什么通道只是排查环境变量污染。reading choices相关报错这类错误一般出现在解析响应时说明返回结构和你预期的不一致。常见原因是 Model ID 填错导致返回的不是标准结构或者 Base URL 少了/api后缀请求打到了错误路径。检查TAOTOKEN_BASE_URL是不是完整的https://taotoken.net/apiModel ID 是不是从控制台复制的准确值。OAuth相关报错如果你用的是需要 OAuth 授权的工具报 OAuth 失败通常是回调地址不对或授权过期。重新走一遍授权流程确认回调地址和工具要求一致。如果是 Claude Code 这类读 settings 的工具检查 settings 里的字段有没有拼错JSON 格式是否合法一个多余的逗号都会导致解析失败。除了这些还有两个高频坑。一是连接池耗尽表现是connection pool exhausted或请求一直排队原因是putconn没在finally里调用或者maxconn设得太小。二是statement_timeout没生效原因是写在了客户端而不是服务端正确写法是放在options参数里传给服务端。这两个坑我在不同项目里都踩过改起来不难但不知道原因时会查很久。排查通用思路先看报错关键词401 查 Keyproxy 查环境变量choices 查 Base URL 和 Model IDOAuth 查授权和配置格式。按这个顺序走大部分问题五分钟内能定位。6. 长期编码与 Agent 场景的接入建议如果你只是偶尔跑个查询脚本上面的配置够用了。但如果你在做长期编码、定时 ETL、或者 Agent 类应用连接管理要更讲究。长期运行的任务不要每次查询都新建连接用连接池复用池子大小根据并发量设一般 5 到 20 之间太小会排队太大会拖垮数据库。每个连接用完必须归还异常路径也要覆盖。Agent 场景下工具调用会频繁发起请求这时候统一通道的价值就体现出来了Base URL、Key、Model ID 集中管理换模型或换额度只需要改一处配置不用在每个脚本里翻。Coding Plan 这类长期编码方案适合需要持续调用、对额度有规划的场景接入方式还是那三件套配置一次到处复用。最后给一个实用建议把连接配置和 Key 配置分离到两个文件数据库参数一个统一通道三件套一个代码里分别读取。这样迁移环境时只改配置不改代码也避免把数据库密码和 API Key 混在一起管理。配置写完先跑一次验证脚本确认连接耗时和查询耗时都在正常范围再上生产。遇到超时先分连接阶段还是执行阶段再按报错关键词定位基本不会卡太久。