1. 为什么单独讲
第 7 篇的 GORM 只讲了 CRUD,连接池没碰。默认连接数不限,流量一上来 MySQL 被连爆,或者连太多拖慢整体。生产第一道坎就是连接池。
2. 连接池参数
sql.DB(GORM 底层)是连接池,三个数定生死:
db, _ := gorm.Open(mysql.Open(dsn)) sqlDB, _ := db.DB() sqlDB.SetMaxOpenConns(50) // 最大打开连接,按 max_connections 和实例数摊 sqlDB.SetMaxIdleConns(10) // 空闲保活数 sqlDB.SetConnMaxLifetime(1 * time.Hour) // 连接最长寿命,防空闲被中间件断
MaxOpenConns 别超过 MySQL 的 max_connections 除以实例数,否则报「too many connections」。
3. 连接泄漏
最常见的坑:查完没关 rows,连接不归还池:
rows, err := db.Raw("...").Rows() if err != nil { return } defer rows.Close() // 必须关,否则连接泄漏
GORM 的 Find/First 自动关,手写 Rows() 要自己 defer Close()。
4. context 超时救连接
下游慢查询占着连接不放,池被耗光。带超时的查询(第 3 篇)让慢查询主动释放:
ctx, cancel := context.WithTimeout(ctx, 800*time.Millisecond) defer cancel() db.WithContext(ctx).Find(&orders)
5. 读写分离
写走主库,读走从库,分摊压力:
var db *gorm.DB // 主库 var dbRead *gorm.DB // 从库 func (r *OrderRepo) Find(id string) (*Order, error) { return findOn(dbRead, id) // 读走从库 } func (r *OrderRepo) Save(o *Order) error { return saveOn(db, o) // 写走主库 }
从库有复制延迟,刚写立马读可能读不到(最终一致),关键读(如刚下单查状态)走主库。
6. 慢查询与索引
db.Debug() 打印 SQL,慢的在 MySQL 开慢查询日志抓。EXPLAIN 看是否走索引。博客另有 MySQL 索引三连(btree/聚簇/写陷阱)可对照。
7. 上线清单
MaxOpenConns 按 max_connections 和实例数摊,别超限。手写 Rows() 必 defer Close,防连接泄漏。查询带 context 超时,慢查询主动释放连接。读多写少做读写分离,但刚写的关键读走主库防延迟。开慢查询日志 + EXPLAIN 定期查慢 SQL。
下一篇讲权限,把第 6 篇的认证补上授权。