commands out of sync. Did you run multiple statements at once?

Go 的 mysql 驱动库 github.com/go-sql-driver/mysql 在连接数据库时,有时会产生一个奇怪的错误

1
"commands out of sync. Did you run multiple statements at once?"

这个错误的字面意思是同一时刻执行了多个 SQL 语句。咋看上会让人感到很迷惑,特别是这个错误有可能发生在数据建立连接时。

例如下面这段代码也会遇到相同的错误信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import (
"database/sql"
"time"

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

func main() {
db, err := sql.Open("mysql", "user:password@/dbname")
if err != nil {
panic(err) // panic,返回上面的错误
}
defer db.Close()
db.Ping()
}

在上面这段代码中,我们只是建立连接,还没有执行语句就发生了错误。说明错误产生的原因并不是像错误本身信息描述的那样。

为了了解这个错误产生的原因,我们打开它的源码,查找错误出现的原因。首先使用 rg 命令从代码中搜索这段文本

1
rg -t go "commands out of sync. Did you run multiple statements at once?"

根据返回的结果,可以看到这是一个在 errors.go 中预定义好的错误。

找到这个错误之后,我们来看下这个错误是在哪被使用的。继续查找 ErrPktSyncMul 这个关键字,发现是在 packets.go 中使用的, 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
func (mc *mysqlConn) readPacket() ([]byte, error) {
// ...

// check packet sync [8 bit]
if data[3] != mc.sequence {
mc.Close()
if data[3] > mc.sequence {
return nil, ErrPktSyncMul
}
return nil, ErrPktSync
}
}

在这个方法中, 客户端从网络连接中读取了 mysql 服务返回的数据包,发现数据包头的 sync 信息与已经读取的 sequence 不相等,并且大于当前 sequence 的,于是认为产生了多语句执行。

但是,我们的情况并不是执行语句,而是在一开始建立连接就失败了,继续查看源码,发现在 Open 的时候,驱动会与数据库握手,同时调用 readPacket 从连接中读取数据包。

猜测是与服务端通信出现了问题,搜索相关的 issue,发现了和我们情况类似的问题 issue #1038。根据 issue 所描述,当 mariadb 服务端拒绝与当前客户端通信时就会出现这个错误。而根据下面的评论,这个错误有可能会因为特定的 mariadb 版本引发,因为 mysql 的服务端实现有 bug。mariadb 的开发者,在评论中回答道问题已经修复了。

到这里,我们终于弄清了这个错误出现的原因,并不是由于我们执行了多行语句,而是因为我们与服务端的通信被拒绝了,错误信息迷惑了我们。

因此,当连接 mysql 时出现了这个错误,可以按照下面的方式去排查

  1. 查看 mysql 的服务版本是不是存在 bug 的版本,如果是的,那么就需要升级服务版本
  2. 查看 mysql 是否达到了错误连接数的上限,如果是的,可以修改服务端配置,调大连接数,FLUSH HOSTS, 再尝试重新连接

commands out of sync. Did you run multiple statements at once?
https://blog.zhangliangliang.cc/post/go-driver-conn-issue.html
作者
Bobby Zhang
发布于
2023年11月16日
许可协议