我刚开始学习Go。当天的课程是将数据库处理程序包装在结构中以避免使用全局范围变量。我以为我到目前为止理解它并希望像之前一样推迟Close()方法,它以堆栈溢出结束。
我找不到为什么会发生这种情况的解释,也没有找到适当的方法。
以下是关键代码:
package exporter
type DB struct {
*sqlx.DB
queriesExecuted int
}
func Open(dataSourceName string) *DB {
connection := sqlx.MustConnect("mysql", dataSourceName)
db := &DB{connection, 0}
return db
}
func (db *DB) Close() {
db.Close() // this is where the stack growth happens
}
func (db *DB) GetArticles() []oxarticle {
...
}
package main
func main() {
exporter := feedexporter.Open("root:pass@/feedexport")
defer exporter.Close()
articles := exporter.GetArticles()
}
一切正常,没有defer exporter.Close(),包括它以:
结尾运行时:goroutine堆栈超过1000000000字节限制
致命错误:堆栈溢出
不关闭连接感觉很糟糕;)处理此问题的方法是什么?
答案 0 :(得分:6)
您正在Close()
方法中触发无限递归:
func (db *DB) Close() {
db.Close() // you're currently IN this exact method!
}
您可能要做的是调用嵌入在自定义Close()
结构中的sqlx.DB
结构的DB
方法。我对sqlx
包不熟悉,但according to the documentation该类型甚至没有Close()
方法。这很可能是因为sqlx.DB
实际上并不代表单个连接,而是透明地创建和关闭连接的连接池:
数据库实例不是连接,而是表示数据库的抽象。这就是创建数据库不会返回错误并且不会出现紧急情况的原因。它在内部维护一个连接池,并在首次需要连接时尝试连接。
documentation深入解释了如何处理此连接池(强调我的):
默认情况下,池会无限制地增长,并且只要池中没有可用的空闲连接,就会创建连接。您可以使用DB.SetMaxOpenConns设置池的最大大小。 未使用的连接标记为空闲,如果不需要则关闭。要避免建立和关闭大量连接,请使用DB.SetMaxIdleConns将最大空闲大小设置为对查询加载敏感的大小。
不小心抓住连接很容易陷入困境。为了防止这种情况:
- 确保Scan()每个Row对象
- 通过Next()每个Rows对象确保Close()或完全迭代
- 确保每个事务都通过Commit()或Rollback()
返回其连接 醇>