Go 27:SQL 超时与连接池饱和怎样传回服务
上篇把任务的领取与终态写进数据库。如果连接池的唯一连接被占住,新的查询还没有抵达 PostgreSQL 就会等待。这样的等待不会因为 SQL 语句本身很快而消失。本文沿用累计任务服务及 PostgreSQL 16.15;完整工程包、SHA-256和独立复跑步骤包含正反实验。
先把等待放在可控的位置
database/sql.DB 是连接池,不是单一连接。在本版 sql.DB.SetMaxOpenConns(1) 下,测试先完成建表、插入 held 任务,再用 db.Conn(context.Background()) 借出唯一的池槽位并暂不归还。此时服务处理 GET、POST、取消这三种 HTTP 请求;它们尚未获得连接。Go 1.27.1 的 DB.Conn 文档说明:池满时,调用会等待可用连接或上下文取消。DBStats 的 InUse 和累计 WaitCount/WaitDuration 能帮助辨认本次是在池里等待,但不是数据库服务端的等待计数。
SQL 模式给每个 HTTP 操作建一个 context.WithTimeout(request.Context(), 2*time.Second);提交、查询和取消所发出的数据库调用都使用它,而不是无期限的背景 context。测试把实例期限调成 60ms:一次 GET、一次 POST、一次取消依次约 61ms 返回 503,本轮 WaitCount 增加 3,InUse=1。没拿到连接时不应把 GET 伪报 404,也不能把取消请求伪报已受理。释放占用连接后,同一服务 GET held 返回 200/QUEUED,新 POST 返回 202;等 worker 实际退出,再查新任务为 200/SUCCEEDED、words=1。202 仍是接收回执,不是分析完成。
这里对池的限制是测试里显式设置的 1;累计服务构造入口只设置 SQL HTTP 调用的默认 2s 期限,不擅自改变调用者配置的池容量。服务可以按部署容量另行设 SetMaxOpenConns。把连接上限调大既会改变等待时机,也会增加数据库并发占用;不能用“不等待了”推断负载整体恢复。
请求期限与数据库取消不是同一个结果
连接已取得、SQL 已经运行的反例用真实 PostgreSQL 执行 SELECT pg_sleep(2),调用方的 context 期限仍是 60ms。本机固定的 lib/pq v1.12.3 返回 pq: canceling statement due to user request (57014),调用方 ctx.Err() 为 context deadline exceeded;之后新查询 SELECT 1 得到 1。驱动的 QueryContext/ExecContext 在 conn_go18.go 通过 watchCancel 尝试另开连接发送 CancelRequest;源码和这一条运行记录可说明当前驱动与数据库在这个场景的表现,不能保证所有网络或所有驱动都会撤销已执行的语句。尤其数据库 UPDATE 的提交应答丢失时,客户端超时不能证明记录没改;SQL 提交也不能证明外部副作用只执行一次。
两类等待的定位不同:池满的测试里 SQL 尚未发出,HTTP 返回 503、释放后恢复;慢查询的测试里 SQL 已在服务端运行,驱动返回具体 PostgreSQL 错误。两组测试不能互相替代。每组单次及 20 次复跑、完整两包测试、race、旧 CLI 正反均有实际命令、stdout、stderr 和退出码,见 writing-plans/go-language/evidence/27/;测试 DSN 缺失会 Skip,不能拿 Skip 后的绿色输出当数据库验收。go test -race 未报告只覆盖运行过的路径。官方 Go 数据库连接管理网页本轮抓取收到 HTTP 502,没有将它冒充已读的一手材料;本机冻结版 go doc、固定驱动源码文件及哈希见 writing-plans/go-language/research/27.md。
练习:① 如果池唯一槽位被借出,查一个不存在的 ID 应该返回 404 还是 503?答:这时查询根本无法完成,返回 503;释放后再查才可能得出 404。② CancelRequest 发出后是否可立即把 RUNNING 写成 CANCELED?答:不可以;第 26 章的取消登记与 worker 退出确认仍须分开,驱动取消一条 SQL 不等于工作者退出。③ 看到 503 后直接重复带外部副作用的 POST 是否安全?答:不能据 503 判定写入一定没提交;需先核对任务 ID、数据库记录及去重契约,本工程尚未提供跨请求幂等键。
本次预算只覆盖 HTTP 路径上发起的 SQL 池等待/查询,不约束请求解码、服务互斥锁等待、建库初始化、worker 的领取与终态写入、失败后的后台补偿或业务完成耗时。真实网络黑洞、提交结果未知后的修复、进程重启恢复、外部副作用去重、其他平台、全站构建与发布仍 NOT_RUN。下一篇处理进程重启时遗留 RUNNING 与 QUEUED 的不同含义。
参考资料
- Go 1.27.1
database/sql.DB.Conn、DB.SetMaxOpenConns、DBStats:固定工具链go doc原文见writing-plans/go-language/evidence/27/doc-*。 github.com/lib/pq v1.12.3的conn_go18.go:固定模块go.sum,实际文件 SHA-2563750092c7b9de29ebec9381de438346d4d52df59d137dd004940e141745dcd8e,符号QueryContext、ExecContext、watchCancel。PostgreSQL 16.15 的服务版本与错误原文见writing-plans/go-language/evidence/27/。






