Go 2000协程并发查询MySQL时Unix Socket连接被拒原因咨询
问题原因
你遇到的Unix Socket连接被拒,和MySQL配置的max_connections上限没有直接关系,核心是三个没覆盖到的问题:
- macOS系统本身对Unix Domain Socket的监听队列长度有默认限制,默认值仅为128。瞬间发起2000个并发连接请求时,绝大多数连接请求会被内核直接丢弃,根本送不到MySQL的连接处理逻辑,直接返回
connection refused。这也是你把并发限制到100就能正常运行的核心原因——100的并发建连量没有超过默认队列的处理能力。 - Go标准库
database/sql的连接池是懒加载模式,你初始化后调用的Ping()方法只会建立1条可用连接。剩余连接会在业务请求到来、现有连接不足时临时创建,2000个协程同时发起查询的瞬间,会同时触发近2000个新建连接请求,瞬间打满Socket监听队列。 - 你的复现代码存在Go版本兼容bug:Go 1.22之前的版本中,goroutine会直接捕获循环的同一个
i变量,最终所有查询的入参都会变成循环结束时的i=2000,这个问题不会触发连接错误,但会导致查询逻辑完全不符合预期。
修复步骤
- 先调大macOS系统的Unix Socket连接队列上限
临时生效执行以下命令:
要永久生效就把上述两个参数写入sudo sysctl -w kern.ipc.somaxconn=4096 sudo sysctl -w kern.ipc.soacceptqueue=4096/etc/sysctl.conf文件,重启设备后配置不会丢失。 - 初始化连接池后做预热,避免瞬时并发建连
不要等业务请求进来才临时创建连接,Ping()校验连通性后,提前建立足够的空闲连接放入连接池,比如提前建1500~2000个连接,预热完成后再启动业务协程发起查询。预热逻辑可以通过循环调用Ping()实现,所有连接建立完成后再继续后续逻辑。 - 补全连接池配置
你目前只设置了最大打开连接数和最大空闲连接数,缺少连接生命周期配置,补充以下参数避免长期运行出现连接异常:mysql.SetConnMaxLifetime(3 * time.Minute) mysql.SetConnMaxIdleTime(1 * time.Minute) - 修复循环变量捕获问题
在启动goroutine前拷贝当前循环的i变量到协程局部作用域,避免所有协程共享同一个循环变量:for i := 0; i < 2000; i++ { wg.Add(1) i := i // 拷贝变量到当前作用域 go func() { defer wg.Done() var someStructVar SomeStruct err := mysql.QueryRow("select a, b from table where id = ?", i).Scan(&someStructVar.a, &someStructVar.b) if err != nil { panic(err) } }() }
补充说明
这个场景下的connection refused和MySQL进程未启动的报错逻辑完全一致:连接请求在操作系统内核层面就被丢弃,没有进入MySQL的连接认证、连接数校验流程,所以不管你把MySQL的max_connections设成多大,都解决不了内核队列丢包的问题。
内容的提问来源于stack exchange,提问作者PerfectCompanion Me
相关产品推荐
相关产品推荐

