Python mysqlclient vs pymysql 的线程安全

2次阅读

不能。mysql连接(mysqlclient和pymysql的connection)均非线程安全,跨线程复用会导致连接丢失、数据错乱或崩溃;必须为每个线程/协程分配独立连接,推荐使用dbutils.pooleddb(mysqlclient)或aiomysql(异步)等线程隔离连接池。

Python mysqlclient vs pymysql 的线程安全

mysqlclient 和 PyMySQL 的 connection 能不能跨线程复用

不能。两个库的 Connection 实例都**不是线程安全的**,哪怕只是并发读,也可能触发 MySQL server has gone away 或数据错乱。这不是 bug,是 MySQL 协议层和 python 驱动实现共同决定的——每个线程必须持有自己的 Connection

常见错误现象:OperationalError: (2013, 'Lost connection to MySQL server during query'),尤其在多线程 + 长连接 + 闲置超时场景下高频出现。

  • PyMySQL 默认开启 autocommit=False,如果一个线程 commit 了,另一个线程还在用同一 connection,事务状态就乱了
  • mysqlclient 底层是 C 扩展,对线程切换更敏感,偶尔会直接 segfault(尤其在 macos 上)
  • 即使加了锁保护 connection,也挡不住 MySQL 自身的 idle timeout(比如 wait_timeout=300),锁住也没用

怎么配线程池才不踩坑

别自己手写 connection 复用逻辑。用带线程隔离的连接池,而不是“共享一个 connection 对象”。

  • mysqlclient 推荐用 DBUtils.PooledDB,配置 maxshared=0(禁用共享)、maxconnections=N(N 是最大线程数),让每个线程 getconn() 拿到的是专属 connection
  • PyMySQL 可以用 pymysqlpool 或直接上 SQLAlchemyQueuePool(默认 behavior 就是 per-Thread connection)
  • 避免在 thread local 外缓存 ConnectionCursor 实例——哪怕只存 1 秒,都可能被其他线程误取

示例(mysqlclient + DBUtils):

from DBUtils.PooledDB import PooledDB<br>pool = PooledDB(<br>    creator=MySQLdb,<br>    maxconnections=20,<br>    maxshared=0,<br>    blocking=True,<br>    host='localhost',<br>    user='root'<br>)<br># 每个线程调用 pool.connection(),拿到的都是独立 connection

立即学习Python免费学习笔记(深入)”;

为什么 cursor 也不能跨线程传

Cursor 是 connection 的附属对象,生命周期绑定 connection。跨线程传 cursor 等价于跨线程操作 connection,一样会出问题。

  • PyMySQL 的 Cursor.execute() 内部会修改 connection 的内部 buffer,多线程写同一 buffer 就是未定义行为
  • mysqlclient 的 cursor.fetchall() 可能触发 C 层的全局状态变更(比如字符集缓存),导致另一线程解析结果出错
  • 即使你只读不写,MySQL 的 packet sequence number 是 connection 级的,线程 A 发包、线程 B 收包,序号对不上直接断连

异步场景(asyncio)下更得小心

PyMySQL 原生不支持 asyncio;mysqlclient 完全不支持。硬套 loop.run_in_executor 时,connection 仍需按线程池方式管理,不能共用。

  • 真要异步,用 aiomysql(基于 PyMySQL 改写,connection 绑定 Event loop)或 asyncmy(纯 async 实现)
  • 别试图把 mysqlclient 的 connection 丢进 executor 后再 await——它没实现 __await__,也不保证线程安全,executor 里多个任务仍可能撞上同一个 connection
  • aiomysql 的 create_pool() 默认就是 per-connection-per-task,比手动管线程池还省心

复杂点在于:connection 的创建/销毁成本高,但线程/协程隔离又不可妥协。折中方案只有两点——用池控数量,用短连接控超时。别的路,基本都绕不过去。

text=ZqhQzanResources