翔云

Just try, don't shy. 最新文章请点击
随笔 - 294, 文章 - 0, 评论 - 27, 阅读 - 49万
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

golang mysql unexpected EOF(invalid connection)

Posted on   翔云123456  阅读(6031)  评论(0编辑  收藏  举报

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

也就是说,当遇到无效的连接时,会自动重新连接。

相关改动:

New Features:
Check connection liveness (#934, #964, #997, #1048, #1051, #1052)

以及

相关的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

编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示