database/sql 的 sql.Open 初始化的是线程安全的连接池句柄而非单个连接,应全局复用;需显式设置 SetMaxOpenConns、SetMaxIdleConns 和 SetConnMaxLifetime 防泄漏与超时,首次查询时才按需建连。

database/sql 的 sql.Open 本身就是一个连接池
很多人误以为 sql.Open 是“真正打开连接”,其实它只初始化一个 *sql.DB 句柄,内部维护着 freeConn []*driverConn 切片、maxIdle、maxOpen 等字段。真正的连接是在第一次调用 db.Query 或 db.Exec 时才按需建立的。
常见错误是漏设连接池参数,导致默认 maxOpen=0(无上限)或 maxIdle=2(太小),在高并发下触发大量新建连接,最终 hit 系统文件描述符限制,报错 too many open files 或 dial tcp: lookup xxx: no such host。
-
db.SetMaxOpenConns(20):控制最大活跃连接数,建议设为后端数据库连接数上限的 70%~80% -
db.SetMaxIdleConns(10):空闲连接保留在池中数量,避免频繁创建/销毁 -
db.SetConnMaxLifetime(30 * time.Minute):强制回收老化连接,防止 MySQL 的wait_timeout断连引发invalid connection
Redis 连接池不能靠 redis.Dial 手动管理
直接用 redis.Dial 每次都新建 TCP 连接,既没复用、也没超时控制,微服务扛不住几十 QPS 就开始报 connect: cannot assign requested address。必须用封装好的连接池——但注意:现在主流已不是 github.com/garyburd/redigo/redis(已归档),而是 github.com/go-redis/redis/v9。
它的池子藏在 redis.NewClient 内部,不是显式 Pool 结构体。你只需配置选项,底层自动复用连接:
立即学习“go语言免费学习笔记(深入)”;
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
-
redis.Options{PoolSize: 20}:等价于旧版的MaxActive,控制最大并发连接数 -
MinIdleConns: 5:保持至少 5 个空闲连接常驻,降低冷启动延迟 -
MaxConnAge: 30 * time.Minute:同 DB 的ConnMaxLifetime,防长连接僵死 - 别手动
client.Close()—— client 是长期存活的单例,关了整个池就废了
MySQL 和 Redis 连接池该不该共用一个初始化时机
应该,而且必须。微服务启动时,DB 和 Cache 都得就绪才能接受请求;但它们失败原因不同,不能混在一起判断。
典型陷阱是把两者塞进同一个 init() 函数或 init block,一旦 Redis 启动慢或网络不通,MySQL 连接也卡住,整个服务起不来。
- 各自独立做健康检查:比如
db.PingContext(ctx)+redisClient.Ping(ctx).Err() - 加超时和重试:用
backoff.Retry或简单time.AfterFunc轮询,最多试 3 次,每次间隔 1s - 失败时 panic 或 log.Fatal,而不是静默忽略 —— 微服务依赖缺失不可降级
连接池句柄怎么在 handler 里安全使用
全局单例 + context 传递是最简洁的做法。不要在每个 handler 里 new 一个 client,也不要传指针参数层层透传。
容易踩的坑是把 *sql.DB 或 *redis.Client 当成 request-scoped 变量塞进中间件上下文,结果被 goroutine 复用出数据竞争 —— 它们本就是并发安全的,直接全局引用即可。
- 定义包级变量:
var DB *sql.DB、var Cache *redis.Client - 在 main 初始化后,立刻调用
DB.Ping()和Cache.Ping().Err()验证可用性 - handler 中直接用:
rows, _ := DB.Query("SELECT ...")、val, _ := Cache.Get(ctx, "key").Result() - 所有操作都带
context.Context,方便超时控制和 cancel 传播
复杂点在于连接池参数调优没有银弹:DB 的 maxOpen 要匹配 MySQL 的 max_connections,Redis 的 PoolSize 要贴合业务并发峰值,而这两个值在压测前后可能差 3 倍。上线前务必用 go tool pprof 看 goroutine 和 heap,确认连接没泄漏、idle 数稳定。

















