数据库连接是昂贵资源。建立连接要经过三次握手、认证、分配资源,耗时几十到几百毫秒。连接池就是复用连接,避免频繁创建销毁。
GORM 底层使用 database/sql 的连接池,配置方法:
sqlDB, err := db.DB()
// 设置最大空闲连接数
sqlDB.SetMaxIdleConns(10)
// 设置最大打开连接数
sqlDB.SetMaxOpenConns(100)
// 设置连接最大存活时间
sqlDB.SetConnMaxLifetime(time.Hour)
// 设置连接最大空闲时间
sqlDB.SetConnMaxIdleTime(time.Minute * 10)
MaxIdleConns
空闲连接池的最大连接数。空闲连接是指用完后放回池中等待复用的连接。
设置太小:高并发时频繁创建连接 设置太大:占用过多资源,连接长期空闲浪费
MaxOpenConns
数据库最大连接数,包括正在使用和空闲的。
设置太大:数据库压力过大,可能被拒绝连接 设置太小:请求排队等待,响应变慢
ConnMaxLifetime
连接最大存活时间,超过后连接会被关闭。
为什么要设置?长时间运行的连接可能遇到:
建议设置比数据库 wait_timeout 小的值。
ConnMaxIdleTime
连接空闲多长时间后关闭。Go 1.15 引入。
区别于 ConnMaxLifetime:ConnMaxLifetime 是连接创建后的总存活时间,ConnMaxIdleTime 是连接空闲的时间。
没有放之四海皆准的配置,要根据实际情况调整。
MaxOpenConns
参考数据库的 max_connections:
-- MySQL
SHOW VARIABLES LIKE 'max_connections';
-- PostgreSQL
SHOW max_connections;
应用 MaxOpenConns 应该小于数据库限制,留出余量给其他应用和管理连接。
如果部署多个实例,要考虑总连接数:
数据库 max_connections = 500
应用实例数 = 5
每个实例 MaxOpenConns = 500 / 5 - 余量 ≈ 80
MaxIdleConns
一般设置为 MaxOpenConns 的 1/2 到 1/4。
流量稳定的服务可以设置高一些,减少连接创建。 流量波动大的服务设置低一些,避免空闲时占用资源。
ConnMaxLifetime
建议 5 分钟到 1 小时。太短会频繁重建连接,太长可能遇到连接失效问题。
// 常见配置
sqlDB.SetConnMaxLifetime(30 * time.Minute)
ConnMaxIdleTime
建议 5-15 分钟。空闲连接保留太久没意义。
sqlDB.SetConnMaxIdleTime(10 * time.Minute)
Go 1.11 提供了连接池统计:
stats := sqlDB.Stats()
fmt.Printf("等待连接数: %d\n", stats.WaitCount)
fmt.Printf("等待时长: %v\n", stats.WaitDuration)
fmt.Printf("最大打开连接数: %d\n", stats.MaxOpenConnections)
fmt.Printf("当前打开连接数: %d\n", stats.OpenConnections)
fmt.Printf("正在使用连接数: %d\n", stats.InUse)
fmt.Printf("空闲连接数: %d\n", stats.Idle)
可以暴露为 Prometheus 指标:
// 伪代码
prometheus.NewGaugeFunc(prometheus.GaugeOpts{
Name: "db_pool_open_connections",
}, func() float64 {
stats, _ := sqlDB.Stats()
return float64(stats.OpenConnections)
})
连接泄漏
症状:OpenConnections 持续增长,直到 MaxOpenConns,然后请求阻塞。
原因:没有正确关闭 Rows 或事务。
// 错误:没有关闭 Rows
rows, _ := db.Raw("SELECT * FROM users").Rows()
for rows.Next() {
// ...
}
// 忘记 rows.Close()
// 正确
rows, _ := db.Raw("SELECT * FROM users").Rows()
defer rows.Close()
for rows.Next() {
// ...
}
连接超时
症状:大量 WaitCount,WaitDuration 很长。
原因:MaxOpenConns 太小,或查询太慢。
解决:增大 MaxOpenConns,或优化慢查询。
连接失效
症状:偶尔出现 "invalid connection" 错误。
原因:ConnMaxLifetime 设置太长,或数据库端关闭了连接。
解决:设置合理的 ConnMaxLifetime,启用数据库端 keepalive。
MySQL
sqlDB.SetMaxOpenConns(100)
sqlDB.SetMaxIdleConns(20)
sqlDB.SetConnMaxLifetime(30 * time.Minute)
MySQL 的 wait_timeout 默认 8 小时,ConnMaxLifetime 设置小一些可以避免问题。
PostgreSQL
sqlDB.SetMaxOpenConns(50)
sqlDB.SetMaxIdleConns(10)
sqlDB.SetConnMaxLifetime(time.Hour)
PostgreSQL 连接比较重,MaxOpenConns 不宜太大。
SQLite
sqlDB.SetMaxOpenConns(1) // 单连接避免锁竞争
SQLite 是文件数据库,多连接写入会锁冲突。
连接池调优没有银弹,关键是:
调优是一个持续的过程,随着业务变化要不断调整。