我刚开始学习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字节限制
致命错误:堆栈溢出
不关闭连接感觉很糟糕;)什么是处理这个问题的方法?
您在方法中触发无限递归Close()
:
func (db *DB) Close() { db.Close() // you're currently IN this exact method! }
什么你大概意思做的是调用Close()
的方法sqlx.DB
是已嵌入到您的自定义结构DB
结构.我对sqlx
包不熟悉,但根据文档,类型甚至没有Close()
方法.这很可能是因为sqlx.DB
它实际上并不代表单个连接,而是一个透明地创建和关闭连接的连接池:
数据库实例不是连接,而是表示数据库的抽象.这就是创建数据库不会返回错误并且不会出现紧急情况的原因.它在内部维护一个连接池,并在首次需要连接时尝试连接.
该文档深入介绍了如何处理此连接池(强调我的):
默认情况下,池增长无限制,并且只要池中没有可用的空闲连接,就会创建连接.您可以使用DB.SetMaxOpenConns设置池的最大大小.未使用的连接标记为空闲,如果不需要则关闭.要避免建立和关闭大量连接,请使用DB.SetMaxIdleConns将最大空闲大小设置为对查询加载敏感的大小.
不小心抓住连接很容易陷入困境.为了防止这种情况
确保Scan()每个Row对象
确保您通过Next()或每个Rows对象完成Close()或完全迭代
确保每个事务通过Commit()或Rollback()返回其连接