Go database/sql 连接池调优:三个参数决定你的接口吞吐

sql.DB 本身就是一个连接池,但默认参数是为"单机演示"设计的。线上服务不调这三个参数,高并发下大概率会出现"连接打满、请求排队、P99 飙升"。 三个关键参数 SetMaxOpenConns(n):允许同时打开的最大连接数,防止把数据

sql.DB 本身就是一个连接池,但默认参数是为”单机演示”设计的。线上服务不调这三个参数,高并发下大概率会出现”连接打满、请求排队、P99 飙升”。

三个关键参数

  • SetMaxOpenConns(n):允许同时打开的最大连接数,防止把数据库打挂的保险丝
  • SetMaxIdleConns(n):连接池保留的空闲连接数,决定突发流量的响应速度
  • SetConnMaxLifetime(d):连接最长存活时间,规避数据库端的 max_age 强制断开

一个常见的错误配置

db, _ := sql.Open("mysql", dsn)
// 什么都没设,默认 MaxOpenConns 是无限制的

无限打开连接意味着:流量一涨,MySQL 的 max_connections 先爆,报错 too many connections,整个服务连带数据库一起雪崩。

正确做法是压住上限:

db.SetMaxOpenConns(50)          // 单实例建议 20~50,按库规格调整
db.SetMaxIdleConns(10)          // 约为 Open 的 1/3~1/5
db.SetConnMaxLifetime(time.Hour) // 小于数据库的 wait_timeout

怎么判断调小了还是调大了

关注这两个指标:

  1. db.Stats().WaitCount —— 等待连接的次数,持续增长说明 MaxOpenConns 太小
  2. db.Stats().MaxIdleConnsClosed —— 因空闲被关闭的连接,数值很高说明 MaxIdleConns 偏大,白建了连接

压测时把 WaitCount 控制在接近 0,同时数据库 CPU 不超过 60%,就是合理区间。

一个容易忽略的坑

sql.Open 不会真正建立连接,它只是校验参数。如果 DSN 写错,错误要到第一次查询才暴露。建议启动时做一次探活:

if err := db.Ping(); err != nil {
    log.Fatalf("数据库连接失败: %v", err)
}

把探活放在健康检查里,比等到用户报障再发现要体面得多。