Go 数据库连接池调优:用 sql.DB 的 SetMaxOpenConns 扛住高并发

test12026-09-162 次阅读

一个被忽略的默认行为

Go 标准库 database/sql 的 sql.DB 不是「一条连接」,而是一个连接池。但很多人以为它是单连接,于是在函数里反复 Open,或者从不开连接数,结果线上突发流量时数据库侧连接被打满,应用也开始排队等待。

默认情况下,sql.DB 没有最大连接数上限(MaxOpenConns = 0 表示无限制),MySQL 服务端 max_connections 一旦被突破就会拒绝新连接,故障就此产生。

三个必须设置的参数

  • SetMaxOpenConns:池中最多同时打开的连接数,对应数据库并发压力上限。
  • SetMaxIdleConns:最大空闲连接数,避免高峰期频繁建连。
  • SetConnMaxLifetime:连接最长存活时间,配合数据库或中间件(如云厂商的空闲回收)防止拿到已被服务端关闭的连接。

可运行示例

package main

import (
	"database/sql"
	"log"
	"time"

	_ "github.com/go-sql-driver/mysql"
)

func newPool(dsn string) (*sql.DB, error) {
	db, err := sql.Open("mysql", dsn)
	if err != nil {
		return nil, err
	}

	// 关键调优:把池子上限钉死,别让默认无限制冲垮数据库
	db.SetMaxOpenConns(50)
	db.SetMaxIdleConns(10)
	db.SetConnMaxLifetime(30 * time.Minute)

	// 验证真能连上,sql.Open 本身不会建连
	if err := db.Ping(); err != nil {
		return nil, err
	}
	return db, nil
}

func main() {
	db, err := newPool("user:pass@tcp(127.0.0.1:3306)/app?parseTime=true")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	var n int
	if err := db.QueryRow("SELECT COUNT(*) FROM orders").Scan(&n); err != nil {
		log.Fatal(err)
	}
	log.Printf("orders = %d", n)
}

参数怎么选

  • MaxOpenConns 不要拍脑袋等于数据库 max_connections,要给其他消费者留余量,常见做法是数据库上限的 60% 到 80%。
  • MaxIdleConns 小于 MaxOpenConns,否则空闲连接会占满开放连接数,导致峰值拿不到新连接。
  • SetConnMaxLifetime 设成比数据库或代理的空闲超时更短,比如云 MySQL 空闲回收通常是几分钟到几十分钟,设 30 分钟比较安全。

监控比设置更重要

db.Stats() 能拿到实时池状态,建议接入指标采集,重点看 InUse(在用连接)是否长期贴近 MaxOpenConns,以及 WaitCount / WaitDuration 是否持续增长——后者意味着已经在排队等连接,说明池子偏小或慢查询拖住了连接。

s := db.Stats()
log.Printf("inUse=%d idle=%d waitCount=%d wait=%v",
	s.InUse, s.Idle, s.WaitCount, s.WaitDuration)
T

test1

文章作者

一个被忽略的默认行为 Go 标准库 database/sql 的 sql.DB 不是「一条连接」,而是一个连接池。但很...

分类
技术
发布时间
2026-09-16
字数
约 1560 字
阅读
2 次