简介:mysql-connector-python-2.1.7.tar.gz 是 MySQL 官方推出的 Python 数据库适配器源码包,面向需要在 Python 项目中访问和管理 MySQL 的开发者,尤其适合使用 Python 2.x 或 3.x 进行后端开发、数据处理与自动化脚本编写的中级学习者。该适配器遵循 DBAPI(PEP 249)规范,提供连接管理、游标操作、事务控制、结果集处理、类型映射、连接池及错误异常处理等能力,并支持多种认证插件,便于在不同 MySQL 环境下稳定交互。压缩包共 123 个文件,约 11.24MB,以 90 个 py 源码文件为核心,辅以 pem 证书、cnf 配置、h 与 c 底层实现文件及少量 txt、csv 说明数据,结构完整,便于阅读源码与本地编译安装。目前已有 364 人学习下载。通过该资源,读者可获取 2.1.7 版本完整源码,理解适配器内部实现与接口设计,并可直接用于项目依赖或二次开发参考。
1. 从 mysql-connector-python-2.1.7.tar.gz 说起:一个老版本驱动包为什么还有人翻出来
如果你在 Python 项目里连 MySQL,大概率写过import mysql.connector。这个包就是 mysql-connector-python,MySQL 官方出的纯 Python 驱动,不依赖 C 库,装完即用。而mysql-connector-python-2.1.7.tar.gz是一个源码分发包,不是 wheel,不是 exe,是那种需要先解压、再python setup.py install或者pip install指向本地路径才能装上的 tar.gz。
问题来了:2024 年了,为什么还有人专门去找 2.1.7 这个版本?我踩过这个坑。一些老系统跑在 Python 2.7 上,或者 Python 3.4/3.5 环境,新版本驱动直接不兼容,报语法错误或者依赖缺失。2.1.7 是少数还能在这些老解释器上跑起来的版本。另外有些内网环境没有外网 pip 源,只能拿 tar.gz 离线装。所以这个标题背后不是“怀旧”,是真实的离线部署和老环境兼容需求。
这篇文章面向两类人:一是手里已经拿到这个 tar.gz,想知道怎么装、怎么配、怎么不翻车;二是正在维护老 Python 项目,需要判断这个版本能不能用、边界在哪。我会按“先搞清楚它是什么 → 怎么装怎么连 → 参数怎么调 → 坑在哪 → 怎么验证”的顺序讲,每一步都给可复现的命令和代码。
2. 拆开 tar.gz 看结构:2.1.7 里到底装了什么
2.1 源码包的目录布局与关键文件
拿到mysql-connector-python-2.1.7.tar.gz之后,先别急着装。解压看一眼目录,能省掉后面很多“为什么 import 失败”的排查时间。
tar -tzf mysql-connector-python-2.1.7.tar.gz | head -40你会看到类似这样的结构:
mysql-connector-python-2.1.7/ ├── setup.py ├── README.txt ├── LICENSE.txt ├── mysql/ │ └── connector/ │ ├── __init__.py │ ├── connection.py │ ├── cursor.py │ ├── protocol.py │ ├── conversion.py │ ├── errors.py │ ├── constants.py │ └── ... └── mysql_connector_python.egg-info/关键点:真正的包代码在mysql/connector/下面,顶层包名是mysql,但mysql目录下只有connector这一个子包。这意味着如果你环境里已经装了别的提供mysql命名空间的库(比如某些老版 MySQLdb 的变体),可能会冲突。我一般会先pip list | grep -i mysql确认一下。
setup.py里定义了版本号、依赖和入口。2.1.7 这个版本的setup.py里install_requires基本是空的,它不强制依赖第三方库,纯 Python 实现协议。这也是它在离线环境好用的原因——一个 tar.gz 丢进去就能装,不用再拉一堆依赖。
2.2 为什么是纯 Python 实现,以及它和 C 扩展驱动的差别
mysql-connector-python 是纯 Python 写的,走 MySQL 客户端/服务端协议。对比 MySQLdb 或者 mysqlclient 那种基于 libmysqlclient C 库的驱动,差别很明显:
| 维度 | mysql-connector-python 2.1.7 | mysqlclient(C 扩展) |
|---|---|---|
| 安装依赖 | 无外部 C 库,纯 Python | 需要 libmysqlclient-dev 和编译工具链 |
| 性能 | 协议解析在 Python 层,吞吐偏低 | C 层解析,批量插入快 |
| Python 2.7 支持 | 支持 | 新版本已放弃 |
| 离线部署 | tar.gz 直接装 | 需要先解决系统库 |
| 协议特性 | 支持 SSL、压缩、连接池(部分) | 依赖 C 库版本 |
选它的理由通常不是性能,而是“装得上”。内网、老系统、没有编译环境,这三点凑齐,纯 Python 就是唯一解。但你要接受它的性能边界:单连接每秒几千次简单查询到顶了,大批量写入要用executemany并且调大batch_size,不然会慢到怀疑人生。
2.3 安装前必须确认的 Python 版本与 pip 行为
2.1.7 支持的 Python 版本范围,从它的setup.py分类器看,覆盖 Python 2.7 到 3.5 左右。如果你在 Python 3.8+ 上硬装,可能会遇到collections.Iterable这类已移除的导入报错。所以第一步永远是确认解释器版本:
python --version python -c "import sys; print(sys.version_info)"如果版本在 3.6 以上,我建议直接换新版本驱动,不要跟 2.1.7 较劲。如果确实锁死在 2.7 或 3.4,那就继续。安装命令有两种写法,区别在于是否走 pip 的构建隔离:
# 方式一:直接 pip 安装本地 tar.gz pip install mysql-connector-python-2.1.7.tar.gz # 方式二:解压后进入目录安装,便于看报错 tar -xzf mysql-connector-python-2.1.7.tar.gz cd mysql-connector-python-2.1.7 python setup.py install方式二的好处是报错信息直接打在终端上,不会藏在 pip 的构建日志里。老版本 pip 对 tar.gz 的处理有时候会先尝试拉取依赖,离线环境会卡住,这时候加--no-deps --no-index:
pip install --no-deps --no-index mysql-connector-python-2.1.7.tar.gz装完验证:
python -c "import mysql.connector; print(mysql.connector.__version__)"输出2.1.7就对了。如果报ImportError,先看是不是当前目录下有个mysql文件夹把包路径遮住了——这是血泪经验,很多人解压后直接在解压目录里跑 Python,结果 import 到的是源码目录而不是安装后的包。
3. 用 2.1.7 连上 MySQL:最小可用代码与连接参数
3.1 一个能跑通的最小连接脚本
装好之后,先别写复杂逻辑,用最小脚本确认能连上。下面这段代码在 Python 2.7 和 3.4 上都能跑:
# minimal_connect.py import mysql.connector from mysql.connector import errorcode # 连接配置:按实际环境替换 config = { 'user': 'app_user', 'password': 'app_pass', 'host': '127.0.0.1', 'port': 3306, 'database': 'test_db', 'charset': 'utf8mb4', 'use_unicode': True, 'connection_timeout': 10, } try: conn = mysql.connector.connect(**config) print("connected, server version:", conn.get_server_info()) cursor = conn.cursor() cursor.execute("SELECT 1") print("select 1 ->", cursor.fetchone()) cursor.close() conn.close() except mysql.connector.Error as err: if err.errno == errorcode.ER_ACCESS_DENIED_ERROR: print("用户名或密码错误") elif err.errno == errorcode.ER_BAD_DB_ERROR: print("数据库不存在") else: print("连接失败:", err)逻辑说明:connect(**config)把字典展开成关键字参数,2.1.7 的connect签名接受这些键。connection_timeout单位是秒,不设的话默认可能很长,网络不通时会卡住。charset设成utf8mb4是为了支持 emoji 和四字节字符,2.1.7 对utf8mb4的支持是完整的,但前提是服务端和表也用这个字符集。
参数说明:use_unicode=True让返回的字符串是 unicode 对象,Python 2 下尤其重要,不然中文会变乱码。port默认 3306,如果走非标准端口必须显式写。database可以省略,连上后再cursor.execute("USE test_db"),但我不推荐,因为连接池场景下容易串库。
3.2 连接参数逐个拆:哪些必须设,哪些设了会翻车
2.1.7 的connect()支持几十个参数,但日常真正要调的没几个。下面这张表是我实际项目里会显式设置的:
| 参数 | 建议值 | 作用与注意点 |
|---|---|---|
| host | IP 或域名 | 用 127.0.0.1 比 localhost 稳,避免走 socket 时的权限差异 |
| port | 3306 | 非标准端口必须写 |
| user / password | 按环境 | 不要硬编码在代码里,用环境变量或配置文件 |
| database | 库名 | 建议显式指定,避免后续 USE 语句 |
| charset | utf8mb4 | 老版本默认可能是 latin1,中文会乱码 |
| use_unicode | True | Python 2 下必须,Python 3 默认就是 |
| connection_timeout | 5~10 | 不设的话网络故障时线程会挂很久 |
| autocommit | False | 默认就是 False,需要自动提交再改 True |
| sql_mode | 按需 | 2.1.7 可以传,但服务端会覆盖,别依赖它 |
有一个参数叫buffered,默认 False。意思是查询结果不一次性拉到客户端,而是按需从服务端读。好处是省内存,坏处是如果你在同一个连接上又发新查询,会报Unread result found。我一般对结果集小的查询设buffered=True,大结果集保持默认并尽快读完。
# buffered 用法 conn = mysql.connector.connect(**config) cursor = conn.cursor(buffered=True) cursor.execute("SELECT id, name FROM users LIMIT 100") rows = cursor.fetchall() # 一次性读完,后续可继续用同一连接3.3 执行增删改查与事务提交的正确姿势
2.1.7 默认不开 autocommit,所以 INSERT/UPDATE/DELETE 之后必须conn.commit(),否则连接关闭时数据回滚。这是新手最容易翻车的地方——代码跑完没报错,但数据没进去。
# crud_demo.py import mysql.connector conn = mysql.connector.connect(**config) cursor = conn.cursor() try: # 插入 cursor.execute( "INSERT INTO users (name, age) VALUES (%s, %s)", ('张三', 28) ) print("inserted id:", cursor.lastrowid) # 更新 cursor.execute( "UPDATE users SET age = %s WHERE name = %s", (29, '张三') ) print("rows affected:", cursor.rowcount) # 查询 cursor.execute("SELECT id, name, age FROM users WHERE name = %s", ('张三',)) for row in cursor.fetchall(): print(row) conn.commit() # 关键:不 commit 等于白干 except mysql.connector.Error as err: conn.rollback() print("操作失败,已回滚:", err) finally: cursor.close() conn.close()参数说明:占位符用%s,不管字段是字符串还是数字,2.1.7 的驱动会做类型转换。不要用 Python 的%或format拼 SQL,那是 SQL 注入的入口。cursor.lastrowid返回自增主键,cursor.rowcount返回受影响行数,UPDATE 时如果值没变,rowcount 可能是 0,这不是错误。
事务边界要自己控制。如果一次业务操作有多条 SQL,把它们放在同一个 try 里,最后统一 commit,异常时 rollback。2.1.7 不支持 savepoint 的嵌套回滚那么细,但基本的事务原子性够用。
4. 参数调优与批量操作:让 2.1.7 在老环境里跑得动
4.1 executemany 的 batch_size 与性能拐点
单条 INSERT 循环一万次,在 2.1.7 上大概要几十秒。换成executemany能降到几秒,但前提是设对batch_size。2.1.7 的executemany内部会把数据按 batch 拼成多值 INSERT 语句,batch 太大反而会因为 SQL 过长被服务端拒绝。
# batch_insert.py import mysql.connector import time conn = mysql.connector.connect(**config) cursor = conn.cursor() data = [(i, 'user_%d' % i) for i in range(10000)] start = time.time() cursor.executemany( "INSERT INTO users (age, name) VALUES (%s, %s)", data ) conn.commit() print("elapsed: %.2fs" % (time.time() - start)) cursor.close() conn.close()逻辑说明:executemany接收 SQL 和可迭代的元组序列。2.1.7 默认会把所有数据一次性拼成一条大 SQL,数据量大时可能超过max_allowed_packet。我一般会手动分批:
batch_size = 500 for i in range(0, len(data), batch_size): cursor.executemany( "INSERT INTO users (age, name) VALUES (%s, %s)", data[i:i+batch_size] ) conn.commit()参数说明:batch_size设 500 到 1000 比较稳,具体看单行数据大小。如果单行有长文本,降到 100。max_allowed_packet服务端默认 4MB 或 16MB,超过就报Packet too large。这个错误在 2.1.7 上不会自动重试,只能自己分批。
4.2 连接池在 2.1.7 里的可用性与替代方案
2.1.7 自带mysql.connector.pooling模块,提供MySQLConnectionPool。但说实话,这个版本的连接池有几个已知问题:池满时行为不够优雅,连接回收依赖pool_reset_session,老 MySQL 服务端可能不支持。
# pool_demo.py from mysql.connector import pooling pool = pooling.MySQLConnectionPool( pool_name='mypool', pool_size=5, pool_reset_session=True, **config ) conn = pool.get_connection() cursor = conn.cursor() cursor.execute("SELECT 1") print(cursor.fetchone()) cursor.close() conn.close() # 归还连接到池,不是真正关闭参数说明:pool_size是池内连接数上限,pool_reset_session为 True 时每次取连接会重置会话变量,避免上一个使用者的SET残留。如果服务端是 MySQL 5.5 以下,这个参数可能报错,改成 False 并自己在业务层清理。
如果连接池不够稳,替代方案是自己用threading.local()做线程级连接复用,或者用 DBUtils 这类第三方池。但在纯离线环境里,多一个依赖就多一份麻烦,我通常先用自带池,压测有问题再换。
4.3 超时、重试与断线重连的边界
老环境网络抖动是常态。2.1.7 的connect()有connection_timeout,但查询执行没有独立的读超时。一个慢查询可能把连接挂死。我一般会在服务端设wait_timeout和interactive_timeout,客户端配合做重试。
# retry_connect.py import mysql.connector import time def get_conn(retries=3, delay=2): for attempt in range(retries): try: return mysql.connector.connect(**config) except mysql.connector.Error as err: print("connect attempt %d failed: %s" % (attempt + 1, err)) if attempt < retries - 1: time.sleep(delay) raise RuntimeError("cannot connect after retries") conn = get_conn()逻辑说明:重试只针对连接建立阶段,不针对查询。查询失败要看错误码,2006 MySQL server has gone away和2013 Lost connection可以重连后重试,但要注意幂等性——INSERT 重试可能产生重复数据。我的习惯是写操作带唯一键,重试时用INSERT IGNORE或ON DUPLICATE KEY UPDATE。
参数说明:retries设 3 次够了,delay用指数退避更好,但 2.1.7 时代很多项目就是固定 sleep。connection_timeout设 5 秒,配合重试,总耗时可控。
5. 避坑与排查:2.1.7 在真实环境里的五个翻车现场
5.1 现象:import mysql.connector 报 SyntaxError
原因:Python 3.8+ 上装了 2.1.7,源码里用了async作为变量名或collections.Iterable,新解释器不认。
解决:确认解释器版本,3.6 以上换新驱动。如果必须用 2.1.7,把解释器降到 3.5 或 2.7。不要试图改源码,改了一处还有十处。
5.2 现象:中文写入后查询出来是问号
原因:连接 charset 没设或设成了 latin1,服务端表字符集也不对。
解决:连接参数加charset='utf8mb4'和use_unicode=True,同时确认服务端SHOW VARIABLES LIKE 'character_set%'里character_set_server是 utf8mb4。表建的时候也要DEFAULT CHARSET=utf8mb4。三处缺一不可。
5.3 现象:executemany 报 Packet too large
原因:一次性提交的数据超过max_allowed_packet。
解决:手动分批,batch_size 降到 500 以下。或者临时调大服务端max_allowed_packet,但生产环境不建议随便调,分批更可控。
5.4 现象:连接池 get_connection 卡住不返回
原因:池内连接被泄漏,没有正确 close 归还。2.1.7 的池不会自动回收超时连接。
解决:确保每次get_connection()后都在 finally 里conn.close()。如果代码路径复杂,用上下文管理器封装。另外pool_size不要设太大,5 到 10 够用,设大了反而容易泄漏。
5.5 现象:查询报 Unread result found
原因:上一个查询的结果没读完,又发了新查询。buffered=False时尤其常见。
解决:要么把结果fetchall()读完,要么创建 cursor 时设buffered=True。我一般对交互式查询用 buffered,对大批量导出用非 buffered 并确保读完。
6. 验证 2.1.7 是否真的可用:一个自检脚本与版本迁移判断
装完、连上、跑完 CRUD 之后,怎么确认这个版本在你的环境里是“真的可用”而不是“碰巧没报错”?我习惯写一个自检脚本,把关键路径都过一遍。
# self_check.py import mysql.connector from mysql.connector import errorcode def check(name, fn): try: fn() print("[PASS] %s" % name) except Exception as e: print("[FAIL] %s -> %s" % (name, e)) conn = mysql.connector.connect(**config) cursor = conn.cursor(buffered=True) check("basic select", lambda: cursor.execute("SELECT 1")) check("utf8mb4 insert", lambda: cursor.execute( "CREATE TEMPORARY TABLE t_chk (v VARCHAR(10) CHARACTER SET utf8mb4)")) check("utf8mb4 roundtrip", lambda: ( cursor.execute("INSERT INTO t_chk VALUES (%s)", ('测试',)), cursor.execute("SELECT v FROM t_chk"), cursor.fetchone() )) check("transaction rollback", lambda: ( conn.rollback(), )) check("executemany", lambda: cursor.executemany( "INSERT INTO t_chk VALUES (%s)", [('a',), ('b',)] )) cursor.close() conn.close()这个脚本覆盖了连接、字符集、事务、批量四个核心能力。如果全 PASS,说明 2.1.7 在你的环境里能承担基本业务。如果有 FAIL,按错误码定位,别硬扛。
关于版本迁移的判断:如果你的项目还在 Python 2.7,短期内没法升级解释器,那 2.1.7 就是合理选择,但要在代码里隔离数据库访问层,方便以后换驱动。如果解释器能升到 3.6+,直接上 mysql-connector-python 8.x,API 大体兼容,主要改connect()里几个废弃参数。迁移时先跑上面的自检脚本,再跑业务回归。
我自己的习惯是:任何老版本驱动上线前,先在这个自检脚本基础上加一条“连续 1000 次查询不报错”的压测,确认连接不会莫名断掉。老版本驱动的玄学问题,往往在长时间运行后才暴露。希望帮到你。
本文还有配套的精品资源,点击获取