英文:
Defer on a struct sqlx.Close() ends in a stack overflow
问题
我刚开始学习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() // 这是导致堆栈增长的地方
}
func (db *DB) GetArticles() []oxarticle {
...
}
package main
func main() {
exporter := feedexporter.Open("root:pass@/feedexport")
defer exporter.Close()
articles := exporter.GetArticles()
}
如果没有使用defer exporter.Close()
,一切都正常,但使用后会出现以下错误:
runtime: goroutine stack exceeds 1000000000-byte limit
fatal error: stack overflow
不关闭连接感觉很糟糕 有什么正确的处理方法吗?
英文:
I just started learning Go. Lesson of the day was to wrap my database handler in a struct to avoid using global scope variables. Thought I understood it so far and wanted to defer the Close() method as I did before, which ended in an stack overflow.
I couldn't find an explanation why this happen, nor what's the propper way to do this.
Here is the key code:
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()
}
Everything works fine without the defer exporter.Close(), including it ends in:
> runtime: goroutine stack exceeds 1000000000-byte limit
>
> fatal error: stack overflow
It feels so bad to not close connections What's the propper way to handle this?
答案1
得分: 8
你的 Close()
方法中触发了无限递归:
func (db *DB) Close() {
db.Close() // 你当前正在这个方法中!
}
你可能想要调用嵌入在自定义 DB
结构体中的 sqlx.DB
结构体的 Close()
方法。我对 sqlx
包不是很熟悉,但是根据文档来看,该类型甚至没有 Close()
方法。这很可能是因为 sqlx.DB
实际上并不代表单个连接,而是一个创建和关闭连接的连接池:
DB 实例不是一个连接,而是表示数据库的抽象。这就是为什么创建 DB 不会返回错误并且不会引发 panic。它在内部维护一个连接池,并在首次需要连接时尝试连接。
文档详细解释了如何处理这个连接池(重点在于我):
默认情况下,连接池是无限增长的,只有在池中没有可用的空闲连接时才会创建新的连接。你可以使用
DB.SetMaxOpenConns
来设置连接池的最大大小。未使用的连接会被标记为空闲,并在不需要时关闭。为了避免创建和关闭大量的连接,请使用DB.SetMaxIdleConns
来设置最大空闲连接数,这个数值应该根据你的查询负载合理设置。
为了避免出现问题,文档提供了以下建议:
- 确保对每个
Row
对象都调用了Scan()
- 确保对每个
Rows
对象要么调用了Close()
,要么完全迭代了所有结果(使用Next()
) - 确保每个事务通过
Commit()
或Rollback()
返回连接
英文:
You're triggering an infinite recursion in your Close()
method:
func (db *DB) Close() {
db.Close() // you're currently IN this exact method!
}
What you're probably meaning to do is calling the Close()
method of the sqlx.DB
struct that's embedded in your custom DB
struct. I'm not that familiar with the sqlx
package, but according to the documentation that type does not even have a Close()
method. This is most probably because the sqlx.DB
does not actually represent a single connection, but a connection pool that creates and closes connections transparently:
>A DB instance is not a connection, but an abstraction representing a Database. This is why creating a DB does not return an error and will not panic. It maintains a connection pool internally, and will attempt to connect when a connection is first needed.
The documentation explains in-depth how to handle this connection pool (emphasis mine):
>By default, the pool grows unbounded, and connections will be created whenever there isn't a free connection available in the pool. You can use DB.SetMaxOpenConns to set the maximum size of the pool. Connections that are not being used are marked idle and then closed if they aren't required. To avoid making and closing lots of connections, set the maximum idle size with DB.SetMaxIdleConns to a size that is sensible for your query loads.
>
>It is easy to get into trouble by accidentally holding on to connections. To prevent this:
>
>1. Ensure you Scan() every Row object
>2. Ensure you either Close() or fully-iterate via Next() every Rows object
>3. Ensure every transaction returns its connection via Commit() or Rollback()
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论