为了账号安全,请及时绑定邮箱和手机立即绑定

当太多的 goroutines 查询 MySQL 时,Go 会出现恐慌

当太多的 goroutines 查询 MySQL 时,Go 会出现恐慌

Go
Smart猫小萌 2023-01-03 15:34:56
我想运行可与 MySQL 数据库一起使用的单独 goroutine。我编写了代码,如果 goroutine 的数量少于 1000,它确实有效。但是当我将它更改为 1000 时,Go 开始恐慌。package mainimport (    "database/sql"    _ "github.com/go-sql-driver/mysql"    "time")func routine(db *sql.DB, id int, ch chan<- string) {    update, _ := db.Prepare("SELECT salary FROM users WHERE id = (?)")    defer update.Close()    var salary string    update.QueryRow(id).Scan(&salary)    ch <- salary}func main() {    n := 1000    ch := make(chan string)    list := make([]string, n)    db, _ := sql.Open("mysql", "root:root@/database")    db.SetConnMaxLifetime(time.Minute * 3)    defer db.Close()    for i := 0; i < n; i++ {        go routine(db, 123, ch)    }    for i := 0; i < n; i++ {        list[i] = <-ch    }}这是错误panic: runtime error: invalid memory address or nil pointer dereference        panic: runtime error: invalid memory address or nil pointer dereferencegoroutine 948 [running]:database/sql.(*Stmt).Close(0x0)        C:/Program Files/Go/src/database/sql/sql.go:2872 +0x37panic({0x4e8b20, 0x689240})        C:/Program Files/Go/src/runtime/panic.go:838 +0x207database/sql.(*Stmt).QueryContext(0x0, {0x5788e8, 0xc000018050}, {0xc000d5bf60, 0x1, 0x1})        C:/Program Files/Go/src/database/sql/sql.go:2767 +0x82database/sql.(*Stmt).QueryRowContext(0x0?, {0x5788e8?, 0xc000018050?}, {0xc000d5bf60?, 0x27?, 0x0?})        C:/Program Files/Go/src/database/sql/sql.go:2845 +0x2cdatabase/sql.(*Stmt).QueryRow(...)        C:/Program Files/Go/src/database/sql/sql.go:2867main.routine(0x0?, 0x0?, 0x0?)        C:/Users/me/Desktop/go/5. MySQL/main.go:13 +0xfbcreated by main.main        C:/Users/me/Desktop/go/5. MySQL/main.go:28 +0xba我不太确定这个错误的原因是什么。该代码确实适用于较少的 goroutines。此外,我尝试使用 SQLite 适配器,并且 1000 个 goroutines 工作得很好。但是 1000 在 MySQL 中不是。你能描述一下如何摆脱这些恐慌,让 1000 甚至 10000 个 goroutines 与数据库一起工作吗?我的 Go 版本是 1.18.3 windows/amd64github.com/go-sql-driver/mysql - v1.6.0
查看完整描述

2 回答

?
一只名叫tom的猫

TA贡献1906条经验 获得超3个赞

检查你的错误,不要忽略它们。例如...

update, _ := db.Prepare("SELECT salary FROM users WHERE id = (?)")

从返回的第二个值db.Prepare是 an error,它将解释出了什么问题,但您忽略了它。如果prepare失败,update将被破坏。当您尝试使用它时,update.QueryRow(id).Scan(&salary)或者defer update.Close()您会感到恐慌。

检查错误并处理它。在这种情况下,打印并返回。

update, err := db.Prepare("SELECT salary FROM users WHERE id = (?)")

if err != nil {

  fmt.Println("db.Prepare failed:", err)

  return

}


// This has to come afterwards else you'll try to close nil.

defer update.Close()

对所有可能返回错误的东西执行此操作。这意味着db.Preparesql.OpenRow.Scan

另请参阅Golang 中的错误和异常处理


注意:MySQL 一次有最大准备语句数。如果将其设置得较低,例如 1024,则可能是问题所在。但是错误应该告诉你。

注意:一遍又一遍地准备、执行和关闭相同的语句会破坏准备语句的意义。在真实的应用程序中,您只需准备一次语句并将其传递给每个 Goroutine。您只会在所有 Goroutine 完成后关闭该语句。


查看完整回答
反对 回复 2023-01-03
?
陪伴而非守候

TA贡献1757条经验 获得超8个赞

鉴于此代码...

update, _ := db.Prepare("SELECT salary FROM users WHERE id = (?)")
defer update.Close()

db.Prepare返回错误时,update将是nil*. 然后您尝试调用nil.Close(),产生您的错误。

不要盲目地调用Closenil 对象。


* 这在阅读文档时并不明显,但从实现(最终落在这一行)中可以清楚地看出,当返回的错误不是 nil时,*Stmt将是nil


查看完整回答
反对 回复 2023-01-03
  • 2 回答
  • 0 关注
  • 134 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号