在一个结构体 sqlx.Close() 上使用 defer 会导致堆栈溢出。

huangapple go评论79阅读模式
英文:

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

不关闭连接感觉很糟糕 在一个结构体 sqlx.Close() 上使用 defer 会导致堆栈溢出。 有什么正确的处理方法吗?

英文:

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 在一个结构体 sqlx.Close() 上使用 defer 会导致堆栈溢出。 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 来设置最大空闲连接数,这个数值应该根据你的查询负载合理设置。

为了避免出现问题,文档提供了以下建议:

  1. 确保对每个 Row 对象都调用了 Scan()
  2. 确保对每个 Rows 对象要么调用了 Close(),要么完全迭代了所有结果(使用 Next()
  3. 确保每个事务通过 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()

huangapple
  • 本文由 发表于 2016年1月12日 05:33:15
  • 转载请务必保留本文链接:https://go.coder-hub.com/34731502.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定