根本原因只有一个:mysql驱动非线程安全,多线程共享connection或cursor会导致包序错乱、段错误、游标关闭等错误;正确做法是使用dbutils.pooleddb,确保每线程独享连接与游标,pool为模块级单例,conn和cursor按需创建并及时归还。

根本原因只有一个:MySQL驱动(如 pymysql、MySQLdb)本身不是线程安全的,多个线程共用同一个 connection 或 cursor 实例时,底层 TCP 包序、状态机、内存访问都会冲突。
多线程共享单个 connection 导致 Packet sequence number wrong
错误现象:pymysql.err.InternalError: Packet sequence number wrong - got 45 expected 0 或直接 segfault(SIGSEGV)。
- 本质是多个线程同时往同一个 socket 写数据,TCP 包被错序或覆盖,MySQL 服务端收到乱序 packet 后拒绝解析
-
MySQLdb在 C 层直接调用libmysqlclient.so,共享连接会触发内核级内存越界,导致段错误 - 哪怕只是读操作,只要多个线程调用同一个
cursor.execute(),也存在竞争条件
直接复用 cursor 引发 “cursor closed” 类错误
错误现象:ValueError: Cursor is closed 或 ProgrammingError: MySQL Connection not available。
- 常见于封装了
self.cursor = self.conn.cursor()的类,在多线程中所有线程都引用同一个实例变量 - 一个线程执行完
cursor.close()后,其他线程再调用execute()就会失败 - 即使没显式 close,连接池归还连接时也可能提前释放底层资源
不设连接池 + 高并发 → Too many connections
错误现象:OperationalError: (1040, 'Too many connections')。
- 每个线程新建
pymysql.connect(),但没及时close(),连接堆积在Threads_connected - MySQL 默认
max_connections=100,20 个线程每批建 10 连接就超限 -
wait_timeout(默认 28800 秒)太长,休眠连接长期不释放,进一步挤占名额
用 DBUtils.PooledDB 是最稳妥的解法
关键点:必须让每个线程获取独立的 connection 和 cursor,且不复用实例变量。
- 不要把
pool.connection()存为类属性或全局变量;每次操作都调用一次pool.connection() - 用完立刻
conn.close()—— 这不会真关连接,而是归还给池子 - 示例片段:
from dbutils.pooled_db import PooledDB
import pymysql
<p>pool = PooledDB(
creator=pymysql,
maxconnections=20,
host='localhost',
user='root',
password='xxx',
database='test'
)</p><p>def worker(task_id):
conn = pool.connection() # 每次获取新连接(实际来自池)
try:
with conn.cursor() as cur: # 自动管理 cursor 生命周期
cur.execute("SELECT ... WHERE id=%s", (task_id,))
return cur.fetchone()
finally:
conn.close() # 归还连接,非销毁</p>
真正容易被忽略的是:连接池对象 pool 必须是模块级单例,不能在函数里反复初始化;而 conn 和 cursor 必须在线程内按需创建、用完即还。跨线程传递连接对象,和不用连接池一样危险。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











