bigywater.tech
Go · 102

database/sql 连接池:MaxOpenConns、空闲与寿命

database/sql 的连接池有 MaxOpenConns、MaxIdleConns、ConnMaxLifetime、ConnMaxIdleTime 四个旋钮,每个影响什么、默认值是否合理?这篇不依赖具体的数据库,用一个计数打开/关闭连接数的假驱动把池的行为一个个量出来,并复现“rows 没关导致卡住”的老问题。

21.4 → 210.8 → 1051.4 ms
50 个并发 20 ms 语句,MaxOpenConns 不限制 / 5 / 1 的总用时
每波再开 8 个
默认 MaxIdleConns=2:10 个并发的突发之后只留 2 个空闲,下一波又要新开 8 个
约 1 秒
ConnMaxIdleTime 设 50 ms 时,空闲连接被清理的实际延迟(清理器最小间隔)

MaxOpenConns:并发上限

实测 · 本机执行输出
  50 个并发 Exec(各 20 ms),MaxOpenConns=0(不限制)    总用时   21.4 ms,驱动打开了 50 个连接;Stats:Open 50、InUse 0、Idle 50、WaitCount 0、WaitDuration 0 ms
  50 个并发 Exec(各 20 ms),MaxOpenConns=5         总用时  210.8 ms,驱动打开了  5 个连接;Stats:Open 5、InUse 0、Idle 5、WaitCount 45、WaitDuration 4741 ms
  50 个并发 Exec(各 20 ms),MaxOpenConns=1         总用时 1051.4 ms,驱动打开了  1 个连接;Stats:Open 1、InUse 0、Idle 1、WaitCount 49、WaitDuration 25739 ms

不限制时 50 个并发语句同时开 50 个连接,21.4 ms 完成;限制为 5 就只开 5 个、其余等待,总用时变成 210.8 ms,WaitCount 45;限制为 1 变成串行(1051.4 ms)。Stats 里的 WaitDuration 是所有等待者的等待时间之和,所以会大于总用时。

MaxIdleConns:默认 2

实测 · 本机执行输出
  默认 MaxIdleConns = 2(未设置)
  第一波 10 个并发 Exec 之后:驱动打开 10 个连接、已关闭 8 个;Stats:Open 2、Idle 2、MaxIdleClosed 8
  第二波 10 个并发 Exec 之后:又打开了 8 个连接
  SetMaxIdleConns(10):第一波打开 10 个,第二波又打开 0 个

默认只保留 2 个空闲连接。10 个并发的突发之后 8 个连接被关掉,下一波突发又要新开 8 个;把 SetMaxIdleConns(10) 之后第二波不再新开。有周期性突发的服务,这个值应该接近突发时的并发量。

ConnMaxLifetime

实测 · 本机执行输出
  ConnMaxLifetime=100ms,串行执行 30 次 Exec(每次间隔 30 ms,约 1.5 秒):驱动共打开 15 个连接;Stats:MaxLifetimeClosed 14
  不设 ConnMaxLifetime 的对照:驱动共打开 1 个连接

设 100 ms 后,1.5 秒的串行执行换了 14 次连接(共打开 15 个);不设则一个连接用到底。它的用途是让连接定期重建,以便负载均衡器、数据库侧的超时和故障切换能生效。

ConnMaxIdleTime 的粒度

实测 · 本机执行输出
  ConnMaxIdleTime=50ms,Exec 之后连接一直空闲; 301 ms 时:Open 1、Idle 1、MaxIdleTimeClosed 0(驱动已关闭 0 个)
  ConnMaxIdleTime=50ms,Exec 之后连接一直空闲; 900 ms 时:Open 1、Idle 1、MaxIdleTimeClosed 0(驱动已关闭 0 个)
  ConnMaxIdleTime=50ms,Exec 之后连接一直空闲;1201 ms 时:Open 0、Idle 0、MaxIdleTimeClosed 1(驱动已关闭 1 个)
  ConnMaxIdleTime=50ms,Exec 之后连接一直空闲;2500 ms 时:Open 0、Idle 0、MaxIdleTimeClosed 1(驱动已关闭 1 个)

设 50 ms,空闲连接在 900 ms 时还在,1201 ms 时才被关。原因在源码里:connectionCleaner 的最小间隔是 1 秒(const minInterval = time.Second)。所以 ConnMaxIdleTime 小于 1 秒没有意义。

rows 必须关闭

实测 · 本机执行输出
  MaxOpenConns=1,第一个 Query 的 rows 没有 Close:第二条语句 301 ms 后失败:context deadline exceeded;Stats:InUse 1、WaitCount 1
  rows.Close() 之后再执行:err = <nil>

MaxOpenConns=1 时,第一个 Query 的 rows 没有关闭,第二条语句等到 ctx 超时(301 ms)失败;rows.Close() 之后恢复。文档(Rows.Close)说:Next 返回 false 且没有更多结果集时 Rows 会自动关闭;提前 break 或 return 就必须 defer rows.Close()(这一点本文没有单独实验)。

文档和限制

实测 · 本机执行输出
  database/sql/sql.go 的 connectionCleaner:const minInterval = time.Second if d < minInterval { d = minInterval }
  SetMaxIdleConns sets the maximum number of connections in the idle connection pool. If MaxOpenConns is greater than 0 but less than the new MaxIdleConns, then the new MaxIdleConns will be reduced to match the MaxOpenConns limit. If n <= 0, no idle connections are retained. The default max idle connections is currently 2. This may change in a future release.
  SetConnMaxLifetime sets the maximum amount of time a connection may be reused. Expired connections may be closed lazily before reuse. If d <= 0, connections are not closed due to a connection's age.

文档写明默认空闲数是 2、将来可能改变;MaxIdleConns 大于 MaxOpenConns 时被降到后者。本文用的是假驱动,真实数据库驱动的建连成本(几毫秒到几十毫秒)没有体现,连接失效重试等行为也没有测。

分步演示

database/sql 连接池
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Go 1.27.1(brew);用 go run / go build / go test 直接编译运行,需要第三方库时经代理从 proxy.golang.org 下载;计时用 time.Now / testing.B,数字每次运行会有小幅抖动。驱动是自己写的:实现 driver.Conn / Stmt / Rows,每条语句睡 20 ms,用原子计数器记录打开/关闭的连接数;Stats 来自 db.Stats();每项只运行一次;读过的文档与源码:go doc database/sql.DB.SetMaxIdleConns / SetConnMaxLifetime,src/database/sql/sql.go 的 connectionCleaner
    • 没有验证:真实数据库驱动的行为(建连开销、失效连接的 ErrBadConn 重试)、事务占用连接的情形、context 取消对连接的影响、MaxIdleConns 与 ConnMaxLifetime 的联合调优、数据库端的最大连接数限制
    京ICP备2026067502号-1京公网安备11010502063487号