database/sql 的连接池有 MaxOpenConns、MaxIdleConns、ConnMaxLifetime、ConnMaxIdleTime 四个旋钮,每个影响什么、默认值是否合理?这篇不依赖具体的数据库,用一个计数打开/关闭连接数的假驱动把池的行为一个个量出来,并复现“rows 没关导致卡住”的老问题。
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(未设置) 第一波 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=100ms,串行执行 30 次 Exec(每次间隔 30 ms,约 1.5 秒):驱动共打开 15 个连接;Stats:MaxLifetimeClosed 14 不设 ConnMaxLifetime 的对照:驱动共打开 1 个连接
设 100 ms 后,1.5 秒的串行执行换了 14 次连接(共打开 15 个);不设则一个连接用到底。它的用途是让连接定期重建,以便负载均衡器、数据库侧的超时和故障切换能生效。
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 秒没有意义。
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 时被降到后者。本文用的是假驱动,真实数据库驱动的建连成本(几毫秒到几十毫秒)没有体现,连接失效重试等行为也没有测。