bigywater.tech
常见报错 · 9

MySQL ERROR 2006 server has gone away 与 2013 Lost connection:各种断法报什么,应答丢了重试为什么会多出一行

MySQL server has gone away(2006)和 Lost connection to MySQL server during query(2013)是长连接、连接池、批量任务里最常见的两个报错。它们看起来都是“连接断了”,原因和处理方式却不同。这篇在本机把每一种断法都复现一遍,再对比连接池的几种防御,最后看一种会让重试出事故的断法。

22 / 0 / 0
40 次使用里连接被服务器关掉后失败的次数:不检查 22 次;recycle、借出前 ping 都是 0 次
2 行
应答丢失(客户端收到 2013)后重试,无唯一键的表里的行数;有唯一键的表挡在 1062,仍是 1 行
2.00 s
客户端 read_timeout=2 到期的时间;此时服务器上 select sleep(6) 还在跑

空闲断连 2006

实测 · 本机执行输出
  新连接的 @@session.wait_timeout = 2
  空闲 3 秒后直接 select 1 -> OperationalError: (2006, "MySQL server has gone away (BrokenPipeError(32, 'Broken pipe'))")
  同一个连接紧接着再 select 1 -> InterfaceError: (0, '')
  另一个连接空闲 3 秒后先 ping(reconnect=True),再 select 1 -> ((1,),)
  (这个 PyMySQL 里 reconnect 参数已经弃用:["The 'reconnect' argument is deprecated. Create a new connection if you want to reconnect."])
  空闲 3 秒后 ping(reconnect=False) -> OperationalError: (2006, "MySQL server has gone away (BrokenPipeError(32, 'Broken pipe'))")

连接空闲超过 wait_timeout 会被服务器关掉,下一次发请求时才发现,报 2006。同一个连接之后是 (0, ''),要新建连接。

查询中途断连 2013

实测 · 本机执行输出
  KILL <连接 id>:select sleep(10)… 在 1.01 s 后 -> OperationalError: (2013, 'Lost connection to MySQL server during query ([Errno 32] Broken pipe)')
      之后同一个连接 select 1 -> InterfaceError: (0, '')
  KILL QUERY <连接 id>:select count(*) from big a, big b, big c… 在 1.02 s 后 -> OperationalError: (1317, 'Query execution was interrupted')
      之后同一个连接 select 1 -> ((1,),)
  服务器进程被 kill -9:select sleep(10) 在 1.02 s 后 -> OperationalError: (2013, 'Lost connection to MySQL server during query ([Errno 32] Broken pipe)')
      之后同一个连接 select 1 -> InterfaceError: (0, '')
      重新连接 -> OperationalError: (2003, "Can't connect to MySQL server on 'localhost' ([Errno 61] Connection refused)")

KILL 连接和 kill -9 服务器都是 2013;KILL QUERY 只报 1317,连接还能用。2006 是发请求时连接已断,2013 是等应答时连接断了。

客户端超时

实测 · 本机执行输出
  read_timeout=2,select sleep(6) -> OperationalError: (2013, 'Lost connection to MySQL server during query (The read operation timed out)'),2.00 s 后返回
  客户端放弃之后,服务器 processlist 里 app 的连接: [('app', 'Query', 2, 'select sleep(6)')](查询还在跑,没有被取消)
  再过 5 秒: ()

read_timeout 到期,客户端报 2013 并断开,但服务器上的语句仍然跑完,可能一直占着锁。要取消需要另开连接 KILL QUERY。

连接池的防御

实测 · 本机执行输出
  40 次里闲置超过 1 秒(一定已被服务器关掉)的次数 = 22;总等待 34.4 秒
  不检查:40 次使用里失败 22 次,主动重建 0 次
  recycle(连接存活超过 0.6 秒就重建):40 次使用里失败 0 次,主动重建 25 次
  借出前 ping:40 次使用里失败 0 次,主动重建 22 次,成功的 18 次 ping 平均每次 0.84 ms

不检查则空闲后的第一次必然失败;recycle 要小于 wait_timeout;借出前 ping 多一个往返,但最稳。

应答丢失时的重试

实测 · 本机执行输出
  表 pay(无唯一键):第一次 INSERT 客户端看到 OperationalError: (2013, 'Lost connection to MySQL server during query');服务器上其实已经有 1 行
      客户端“重试”一次 -> ok;表里现在 2 行
  表 pay_u(order_no 唯一):第一次 INSERT 客户端看到 OperationalError: (2013, 'Lost connection to MySQL server during query');服务器上其实已经有 1 行
      客户端“重试”一次 -> IntegrityError: (1062, "Duplicate entry 'A100' for key 'pay_u.order_no'");表里现在 1 行

2013 不保证语句没执行。写操作重试必须幂等,最直接的办法是唯一键,让重复的请求被 1062 挡住。

分步演示

连接断开
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / MySQL 26.10.0 与 Redis 8.10.2(Homebrew,每个实验自己起实例,端口 9150 以上,只监听 127.0.0.1,不碰本机已有的实例)/ Python 3.15.0 + PyMySQL(按各篇注明用到哪些及版本);报错文本都是真实复现时原样捕获的;计时用 time.perf_counter,数字每次运行会有小幅抖动。本篇用自己起的 MySQL 26.10.0 实例,PyMySQL 2.2.8(unix socket 与本机 TCP);应答丢失的实验用我自己写的 TCP 代理(客户端 ssl_disabled=True,代理看到 INSERT 就转发、收下应答、断开客户端);连接池的三种策略是我写的最小模拟,不是 SQLAlchemy / HikariCP 等实际的池
    • 没有验证:Python 以外的驱动(JDBC、mysqlclient、Go)遇到同样情形报什么码;net_read_timeout / net_write_timeout / interactive_timeout 各自的触发条件;服务器重启(而不是 kill -9)时客户端看到的报错;TLS 连接下代理丢应答的效果;SQLAlchemy 的 pool_pre_ping / pool_recycle、HikariCP 的 maxLifetime 与 keepaliveTime 的实际行为;防火墙或负载均衡器静默断开空闲 TCP 连接时的表现(这里全是服务器主动关闭)
    京ICP备2026067502号-1京公网安备11010502063487号