当有太多的goroutine查询MySQL时,Go会出现panic。

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

Go panics when too many goroutines querying MySQL

问题

我想运行独立的goroutine来与MySQL数据库交互。我编写了代码,如果goroutine的数量小于1000,它可以正常工作。但是当我将它更改为1000时,Go就会出现panic错误。

以下是错误信息:

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

我不太确定这个错误的原因。这段代码在较少的goroutine下可以正常工作。此外,我尝试使用SQLite适配器,1000个goroutine可以正常工作。但是在MySQL中,1000个goroutine就不行了。

请问你能描述一下如何解决这些panic错误,并使1000甚至10000个goroutine与数据库正常工作吗?


我的Go版本是1.18.3 windows/amd64
github.com/go-sql-driver/mysql - v1.6.0

英文:

I want to run separate goroutines that would work with a MySQL database. I wrote the code and it does work if the amount of goroutines is less than a 1000. But than I change it to a 1000, Go starts to panic.

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
	}
}

And here's the error

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

I'm not really sure what is the reason of this error. The code does work with less goroutines. In addition, I tried to use SQLite adapter, and 1000 goroutines worked just fine. But 1000 in MySQL is not.

Could you please describe how to get rid of these panics and make 1000 or even 10 thousands of goroutines work with the database?


My Go version is 1.18.3 windows/amd64
github.com/go-sql-driver/mysql - v1.6.0

答案1

得分: 3

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

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

db.Prepare 返回的第二个值是一个 error,它会解释出错的原因,但你忽略了它。如果 prepare 失败,update 将会被破坏。当你尝试在 update.QueryRow(id).Scan(&salary)defer update.Close() 中使用它时,会引发 panic。

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

update, err := db.Prepare("SELECT salary FROM users WHERE id = (?)")
if err != nil {
  fmt.Println("db.Prepare failed:", err)
  return
}

// 这必须放在后面,否则你将尝试关闭 nil。
defer update.Close()

对于可能返回错误的所有操作都要这样做。这意味着 db.Preparesql.OpenRow.Scan

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


注意:MySQL 在同一时间内有 最大的预处理语句数限制。如果设置得很低,例如 1024,可能会出现问题。但错误信息应该会告诉你。

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

英文:

Check your errors, don't ignore them. For example...

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

The second value returned from db.Prepare is an error which will explain what went wrong, but you're ignoring it. If prepare fails, update will be corrupted. When you try to use it in update.QueryRow(id).Scan(&salary) or defer update.Close() you get a panic.

Check the error and deal with it. In this case, print it and return.

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()

Do this for everything which can return an error. That means db.Prepare, sql.Open, and Row.Scan.

See also Errors and Exception Handling in Golang.


Note: MySQL has a maximum number of prepared statements at one time. If this is set low, for example 1024, that could be the problem. But the error should tell you.

Note: Preparing, executing, and closing the same statement over and over defeats the point of prepared statements. In a real application you'd prepare the statement once and pass it into each Goroutine. You'd only close the statement once all Goroutines are complete.

答案2

得分: 1

给定这段代码...

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

db.Prepare 返回一个错误时,update 将会是 nil。然后你尝试调用 nil.Close(),导致了错误。

不要盲目地在一个空对象上调用 Close


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

英文:

Given this code...

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

When db.Prepare returns an error, update will be nil*. You then attempt to call nil.Close(), yielding your error.

Don't blindly call Close on a nil object.


* This is not obvious from reading the documentation, but it's clear from the implementation (which ultimately lands on this line) that when the returned error is not nil, the *Stmt will be nil.

huangapple
  • 本文由 发表于 2022年7月16日 08:26:26
  • 转载请务必保留本文链接:https://go.coder-hub.com/73000700.html
匿名

发表评论

匿名网友

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

确定