golang mysql unexpected EOF(invalid connection)

1.問題

在使用go-sql-driver/mysql連接MySQL 服務過程,隔一段時間,會報MySQL連接錯誤:

[mysql] 2020/05/09 02:02:01 packets.go:36: unexpected EOF
2020-05-09 02:02:01 ERROR goroutine 59835131 invalid connection

排查下來,是由於使用無效的連接導致的。

基本場景是:

  • client 連接MySQL,執行SQL後,不立刻關閉連接。client保留連接在連接池中。
  • 接着,MySQL服務發生重啓,
    或者連接超過最大時長(由wait_timeout定義,一般是8小時), MySQL服務端關閉了連接。
  • 下次 client 執行SQL,從連接池中拿到無效的連接,就會報以上錯誤。

問題驗證

驗證代碼操作步驟如下:

  1. 首先,執行SQL。
  2. 接着,sleep 60s。
  3. 這期間,將MySQL服務重啓一下。
  4. 程序 sleep 後,再次執行 SQL。

代碼如下:

import (
        "database/sql"
        "log"
        "time"

        _ "github.com/go-sql-driver/mysql"

)

var DB *sql.DB
var dataBase = "root:Aa123456@tcp(192.168.1.101:3306)/?loc=Local&parseTime=true"

func mysqlInit() {
        var err error
        DB, err = sql.Open("mysql", dataBase)
        if err != nil {
                log.Fatalln("open db fail:", err)
        }

        err = DB.Ping()
        if err != nil {
                log.Fatalln("ping db fail:", err)
        }
}

func main() {
        mysqlInit()

        for {
                execSql()
                time.Sleep(60*time.Second)
        }
}

func execSql() {
        _, err := DB.Exec("select 1")
        if err != nil {
                log.Println("exec sql failed:", err)
                return
        }

        log.Println("success")
}

看下程序輸出:

2020/05/24 12:00:49 success
[mysql] 2020/05/24 12:01:49 packets.go:36: unexpected EOF
2020/05/24 12:01:49 exec sql failed: invalid connection
2020/05/24 12:02:49 success

從測試結果可以看到,當MySQL服務重啓後,原先的連接會失效,當再次被使用時,會報錯。

go-sql-driver/mysql 在這裏的實現上,並不會主動把問題連接從連接池中剔除,或者連接報錯後,自動重連。

2.解決方案

方案一 升級 mysql driver

go-mysql-driver升級到 Version 1.5 (2020-01-07)。

測試結果輸出如下:

2020/05/24 15:11:33 success
[mysql] 2020/05/24 15:12:33 packets.go:123: closing bad idle connection: EOF
2020/05/24 15:12:33 success
2020/05/24 15:13:33 success

也就是說,當遇到無效的連接時,會自動重新連接。

相關的bug fix 記錄,見Bugfixes:

Mark connections as bad on error during ping (#875)
Mark connections as bad on error during dial (#867)

方案二 設置連接複用時間

如果暫時無法升級go-mysql-driver,那麼可以通過SetConnMaxLifetime()設置連接複用時間,連接默認是永久複用的。

連接複用時間表示連接使用多長時間後,會自動關閉。

需要注意的是,如果連接正在使用中,即使超過連接複用時間,也不會立刻關閉,而是等到連接不再使用後,纔會關閉。

例如,設置連接複用時間爲60s。

db.SetConnMaxLifetime(60 * time.Second)

至於設置多長時間,需要根據自己的場景需要。

另外,有些地方建議使用SetMaxIdleConns()設置idle 連接爲0,這個是不推薦的。

這樣的設置,會導致每次執行SQL,都會建立新的連接。

3.參考

packets.go:36: unexpected EOF (Invalid Connection)

MaxOpenConns, MaxIdleConns, ConnMaxLifetime的理解和調優
Configuring sql.DB for Better Performance

go-sql-driver changelog

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章