Go操作PostgreSQL报错too many clients already求助排查
问题原因及解决方法
问题根源
你的程序出现pq: sorry, too many clients already错误,核心原因在getCreateCurrency函数的连接管理上:
当需要新增币种时,你先通过db.Query执行SELECT查询,而defer res.Close()是在函数返回时才执行的。这意味着在执行后续的INSERT操作时,SELECT查询占用的数据库连接还没有被释放,同时INSERT操作又会从连接池获取一个新连接。每处理一个需要新增的币种,就会占用2个连接。当处理到第50个这类币种时,连接数就达到了PostgreSQL设置的max_connections=100阈值,后续请求自然会被拒绝。
解决方法
方法1:用QueryRow替代Query执行SELECT(推荐)
QueryRow会自动管理连接生命周期,调用Scan()后立即释放连接,无需手动关闭Rows对象,代码更简洁且安全:
func getCreateCurrency(db *sql.DB, rate Rate) int { var id int // 使用QueryRow执行SELECT,自动处理连接 err := db.QueryRow("SELECT id FROM currency WHERE symbol = $1", rate.Symbol).Scan(&id) switch { case err == nil: // 查询到币种,直接返回ID return id case err == sql.ErrNoRows: // 未找到,执行INSERT并返回新ID err = db.QueryRow(` INSERT INTO currency (name, symbol) VALUES ($1, $2) RETURNING id `, rate.Name, rate.Symbol).Scan(&id) chk(err) return id default: // 处理其他查询错误 chk(err) } return id }
方法2:手动提前关闭Rows对象(不推荐)
如果坚持使用db.Query,可以在确认不需要Rows后手动关闭,避免连接被长时间持有:
func getCreateCurrency(db *sql.DB, rate Rate) int { var id int res, err := db.Query("SELECT id FROM currency WHERE symbol = $1", rate.Symbol) chk(err) defer res.Close() if res.Next() { err = res.Scan(&id) chk(err) return id } // 手动关闭Rows,提前释放连接 res.Close() // 执行INSERT操作 err = db.QueryRow(` INSERT INTO currency (name, symbol) VALUES ($1, $2) RETURNING id `, rate.Name, rate.Symbol).Scan(&id) chk(err) return id }
额外修复:避免parseRates中的潜在连接泄漏
你的parseRates函数中,执行rate表插入时使用了db.QueryRow但未调用Scan(),这会导致连接被永久占用(QueryRow返回的Row必须调用Scan()才会释放连接)。建议改用db.Exec执行无返回结果的INSERT:
// 替换原有的db.QueryRow语句 _, err := db.Exec(`INSERT INTO rate (currency_id, rate) VALUES ($1, $2)`, currencyId, price) if err != nil { log.Printf("Failed to insert rate for %s: %v", rate.Symbol, err) continue }
修复原理
改用QueryRow后,SELECT操作的连接会在Scan()完成后立即释放,执行INSERT时只会占用一个连接,避免了同时持有两个连接的情况,从而不会快速耗尽PostgreSQL的连接数。同时修复parseRates中的INSERT操作,彻底杜绝潜在的连接泄漏问题。
内容的提问来源于stack exchange,提问作者James May
相关产品推荐
相关产品推荐

