package main import ( "database/sql" "fmt" _ "github.com/lib/pq" "sync" ) func main() { db, _ := sql.Open("postgres", fmt.Sprintf("host=%s dbname=%s user=%s sslmode=disable", "localhost", "dbname", "postgres")) defer db.Close() db.SetMaxOpenConns(15) var wg sync.WaitGroup for i := 0; i < 15; i++ { wg.Add(1) go func() { defer wg.Done() //#1 rows, _ := db.Query("SELECT * FROM reviews LIMIT 1") for rows.Next() { //#2 db.Exec("SELECT * FROM reviews LIMIT 1") } }() } wg.Wait() }
查询1打开15个连接,rows.Next()执行后将关闭它们。但是rows.Next()将永远不会执行,因为它包含db.Exec()等待自由连接的内容。
rows.Next()
db.Exec()
如何解决这个问题呢?
你有一个僵局。在最坏的情况下,您有15个goroutine持有15个数据库连接,而所有这15个goroutine都需要一个新的连接才能继续。但是要获得新的连接,就必须前进并释放一个连接:死锁。
链接的维基百科文章详细介绍了防止死锁的方法。例如,代码执行仅在拥有需要(或将要使用)的所有资源时才进入关键部分(锁定资源)。在这种情况下,这意味着您将必须保留2个连接(恰好2个;如果只有1个可用,请保留并等待),如果您有2个,则仅继续进行查询。但是在Go中,您无法提前保留连接。在执行查询时,将根据需要分配它们。
通常应避免这种模式。您不应该编写首先保留(有限)资源(在这种情况下为db连接)的代码,然后再释放它,它需要另一个资源。
一个简单的解决方法是执行第一个查询,将其结果保存(例如保存到Go切片中),完成后再进行后续查询(但也不要忘记先关闭sql.Rows)。这样,您的代码不需要同时连接2个连接。
sql.Rows
并且不要忘记处理错误!为了简洁起见,我省略了它们,但是您不应在代码中使用它们。
它看起来像这样:
go func() { defer wg.Done() rows, _ := db.Query("SELECT * FROM reviews LIMIT 1") var data []int // Use whatever type describes data you query for rows.Next() { var something int rows.Scan(&something) data = append(data, something) } rows.Close() for _, v := range data { // You may use v as a query parameter if needed db.Exec("SELECT * FROM reviews LIMIT 1") } }()
请注意,rows.Close()应将其作为defer语句执行,以确保它将被执行(即使出现紧急情况)。但是,如果仅使用defer rows.Close(),则仅在执行后续查询后才执行,因此不会防止死锁。因此,我将其重构为在另一个可以使用的函数(可能是匿名函数)中调用它defer:
rows.Close()
defer
defer rows.Close()
rows, _ := db.Query("SELECT * FROM reviews LIMIT 1") var data []int // Use whatever type describes data you query func() { defer rows.Close() for rows.Next() { var something int rows.Scan(&something) data = append(data, something) } }()
还要注意,在第二个for循环中,由捕获的prepared语句(sql.Stmt)DB.Prepare()可能是执行多次相同(参数化)查询的更好选择。
for
sql.Stmt
DB.Prepare()
另一个选择是在新的goroutine中启动后续查询,以便在释放当前锁定的连接(或任何其他goroutine锁定的任何其他连接)时发生在其中执行的查询,但是如果没有显式同步,您将无法控制何时他们被处决了。它可能看起来像这样:
go func() { defer wg.Done() rows, _ := db.Query("SELECT * FROM reviews LIMIT 1") defer rows.Close() for rows.Next() { var something int rows.Scan(&something) // Pass something if needed go db.Exec("SELECT * FROM reviews LIMIT 1") } }()
为了使您的程序也等待这些goroutine,请使用WaitGroup已经生效的:
WaitGroup
// Pass something if needed wg.Add(1) go func() { defer wg.Done() db.Exec("SELECT * FROM reviews LIMIT 1") }()