mysql错误码 2013 解决方案
以下的方案是 脚本运行场景下 是一个非常合理的 轻量级连接管理方案。【不是特定接口下,而是面对长时间地读写数据库的脚本操作】
有很多时候 有这样的情况发生,就是用脚本去爬某个服务,需要持续不断地写数据库,但是运行一段时间后出现了这样的错误:
(2013, 'Lost connection to MySQL server during query')
这是 MySQL 客户端(例如 Python、Go、Django、GORM 等)在执行 SQL 期间与 MySQL 服务端的连接中断,是 MySQL 经典错误码 2013。
- 客户端已经成功连上了数据库;
- 发起了一个 SQL 查询;
- 在查询执行过程中,连接断掉了;
- 然后就抛出了 (2013, ‘Lost connection to MySQL server during query’)。
发生的现象:
1.查询耗时过长,超时断开
- 例如执行了一个大 SQL 或慢查询,超过了服务器设置的 net_read_timeout 或 net_write_timeout。
- 默认 MySQL 的超时时间有些比较短(比如 30 秒)。
2.网络中断
- 容器重启、服务器短暂断网、Docker/Pod 偶发 kill 等;
- 容器之间网络不稳定,尤其是跨节点通信时。
3.MySQL 服务端崩溃或 kill
- MySQL 本身宕机了;
- 被外部 OOM 或 kill -9;
- 或者服务自动重启(如 K8s liveness probe 触发)。
4.连接池中断但没检测出来
- 客户端复用了一个连接池中已失效的连接;
- 当调用时才发现其实连接早就断了。
5.MySQL 设置连接超时(wait_timeout、interactive_timeout)
- 某连接空闲太久,被服务端断掉了;
- 尤其是在连接池中重用旧连接的时候。
但是我的代码 都没有这样的情况,不过我的代码因为是纯 sql,每一个函数都会手动开启数据库关闭数据库方式,这就是导致了频繁连接和释放数据库,这也是会导致 2013的原因。
✅ 为什么频繁连接/关闭会导致 2013 错误?
📍 原因一:连接尚未完全建立或被服务端拒绝时发起查询
- MySQL 建立连接是有成本的(TCP握手、认证、初始化环境);
- 如果你刚打开连接就马上发查询,而这个连接底层尚未 ready,可能就出现“连接中断”;
- 更极端的是 MySQL 暂时无法处理新的连接(连接数过多、临时压力高),也会出现连接抛错。
📍 原因二:频繁创建连接,容易超出连接数限制或引发资源紧张
- MySQL 默认最大连接数有限制,比如 max_connections = 151;
- 如果你的程序高并发,每个请求都创建连接不复用,容易触发服务器资源异常,导致连接失败、数据传输中断等。
📍 原因三:连接关闭得太早或查询未结束即关闭
- 某些地方关闭连接太早,甚至在异步任务或查询还没拿到结果就 conn.close(),会导致:
因为我是脚本运行的,所以有很大概率会造成这个现象,之前没有彻底解决,就是对每个数据库操作直接 sleep 下,避免短时间频繁创建,但是还是发现治标不治本,因为运行太慢了。
所以我采取了以下的解决办法。
因为我在每个函数参数里面 没有加 session 的内容,如果对每个函数这样更改,成本太高 容易出错,对涉及的函数还需要改加测试。
全局管理+定时释放
所以采取了全局变量+ 定时释放的逻辑操作。
from libs.sync_mysql_sdk import SyncMySQLSDK
from report.db_source import dsn
from utils.time_convert import get_month_start_and_end_datetime
import time
import threading# 🔥 全局单例连接池管理器
class DBManager:def __init__(self):self._mirror_sdk = Noneself._saas_sdk = Noneself._last_use_time = time.time()self._lock = threading.Lock()self._timeout = 30 # 5分钟空闲就释放连接def get_dsn_sdk(self):with self._lock:if self._mirror_sdk is None:self._mirror_sdk = SyncMySQLSDK(dsn=mirror_dsn,min_size=1,max_size=5,read_timeout=60)self._mirror_sdk.connect()print("创建 mirror 数据库连接")self._last_use_time = time.time()return self._mirror_sdkdef check_and_release(self):"""检查并释放空闲连接"""with self._lock:current_time = time.time()if current_time - self._last_use_time > self._timeout:if self._mirror_sdk:self._mirror_sdk.close()self._mirror_sdk = Noneprint("释放 mirror 数据库连接")if self._saas_sdk:self._saas_sdk.close()self._saas_sdk = Noneprint("释放 saas 数据库连接")def force_release(self):"""强制释放所有连接"""with self._lock:if self._mirror_sdk:self._mirror_sdk.close()self._mirror_sdk = Noneprint("强制释放 mirror 数据库连接")if self._saas_sdk:self._saas_sdk.close()self._saas_sdk = Noneprint("强制释放 saas 数据库连接")# 🔥 全局单例实例
_db_manager = DBManager()# 🔥 简单的接口函数
def get_dsn_sdk():return _db_manager.get_dsn_sdk()def release_idle_connections():"""释放空闲连接 - 在你的主程序里定期调用"""_db_manager.check_and_release()def force_release_all():"""强制释放所有连接 - 程序结束时调用"""_db_manager.force_release()
每个数据库操作的函数直接去调用get_dsn_sdk() 这个函数就可以拿到这个方法了。
然后对于按天操作的数据库在 for 循环开始release_idle_connections() 手动去释放空间,同时设置了 timeout 是 30s,这样两个方面可以避免数据库长时间的连接问题了。
✅ 1.解决了频繁连接释放的问题
- 避免每个函数都重新 .connect(),大大降低了数据库连接建立的成本。
- 通过全局变量 + 单例管理,保持了连接的复用性,这是解决 2013 的关键。
✅ 2.封装了 SDK 获取逻辑
- 调用者只需要 get_dsn_sdk(),屏蔽了连接创建/释放的细节,提升了代码复用性和安全性。
✅ 3.提供了定期自动释放机制
- check_and_release() + _timeout 机制,是非常合理的空闲连接回收策略,防止连接泄漏。
- 你还额外暴露了 force_release_all() 用于脚本退出清理,非常严谨。
✅ 4.加了线程锁 threading.Lock()
- 如果你的脚本是多线程并发执行的,这点尤其重要,防止连接状态被并发读写污染。