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

我想运行可与 MySQL 数据库一起使用的单独 goroutine。我编写了代码,如果 goroutine 的数量少于 1000,它确实有效。但是当我将它更改为 1000 时,Go 开始恐慌。


package main


import (

    "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 dereference


goroutine 948 [running]:

database/sql.(*Stmt).Close(0x0)

        C:/Program Files/Go/src/database/sql/sql.go:2872 +0x37

panic({0x4e8b20, 0x689240})

        C:/Program Files/Go/src/runtime/panic.go:838 +0x207

database/sql.(*Stmt).QueryContext(0x0, {0x5788e8, 0xc000018050}, {0xc000d5bf60, 0x1, 0x1})

        C:/Program Files/Go/src/database/sql/sql.go:2767 +0x82

database/sql.(*Stmt).QueryRowContext(0x0?, {0x5788e8?, 0xc000018050?}, {0xc000d5bf60?, 0x27?, 0x0?})

        C:/Program Files/Go/src/database/sql/sql.go:2845 +0x2c

database/sql.(*Stmt).QueryRow(...)

        C:/Program Files/Go/src/database/sql/sql.go:2867

main.routine(0x0?, 0x0?, 0x0?)

        C:/Users/me/Desktop/go/5. MySQL/main.go:13 +0xfb

created 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/amd64

github.com/go-sql-driver/mysql - v1.6.0


Smart猫小萌
浏览 135回答 2
2回答

一只名叫tom的猫

检查你的错误,不要忽略它们。例如...update,&nbsp;_&nbsp;:=&nbsp;db.Prepare("SELECT&nbsp;salary&nbsp;FROM&nbsp;users&nbsp;WHERE&nbsp;id&nbsp;=&nbsp;(?)")从返回的第二个值db.Prepare是 an&nbsp;error,它将解释出了什么问题,但您忽略了它。如果prepare失败,update将被破坏。当您尝试使用它时,update.QueryRow(id).Scan(&salary)或者defer update.Close()您会感到恐慌。检查错误并处理它。在这种情况下,打印并返回。update, err := db.Prepare("SELECT salary FROM users WHERE id = (?)")if err != nil {&nbsp; fmt.Println("db.Prepare failed:", err)&nbsp; return}// This has to come afterwards else you'll try to close nil.defer update.Close()对所有可能返回错误的东西执行此操作。这意味着db.Prepare、sql.Open和Row.Scan。另请参阅Golang 中的错误和异常处理。注意:MySQL 一次有最大准备语句数。如果将其设置得较低,例如 1024,则可能是问题所在。但是错误应该告诉你。注意:一遍又一遍地准备、执行和关闭相同的语句会破坏准备语句的意义。在真实的应用程序中,您只需准备一次语句并将其传递给每个 Goroutine。您只会在所有 Goroutine 完成后关闭该语句。

陪伴而非守候

鉴于此代码...update,&nbsp;_&nbsp;:=&nbsp;db.Prepare("SELECT&nbsp;salary&nbsp;FROM&nbsp;users&nbsp;WHERE&nbsp;id&nbsp;=&nbsp;(?)") defer&nbsp;update.Close()当db.Prepare返回错误时,update将是nil*.&nbsp;然后您尝试调用nil.Close(),产生您的错误。不要盲目地调用Closenil 对象。* 这在阅读文档时并不明显,但从实现(最终落在这一行)中可以清楚地看出,当返回的错误不是&nbsp;nil时,*Stmt将是nil。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go