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