Python连接旧版MySQL:轻松解决mysql_old_password认证错误
2025-04-04 03:56:32
搞定 Python 连接老旧 MySQL 的 mysql_old_password
认证头疼事
写 Python 脚本连数据库是家常便饭,但有时会撞上一些历史遗留问题,比如连接一个还在用老掉牙的 mysql_old_password
认证方式的 MySQL 5.5.x 数据库。更头疼的是,你还没有数据库的管理员权限,改不了服务器设置。
这时候,如果你用 MySQLdb
(也就是 mysqlclient
的前身,常用于 Python 2.7) 去连接,很可能就直接收获一个报错大礼包,类似下面这样:
_mysql_exceptions.OperationalError: (2059, "Authentication plugin 'mysql_old_password' cannot be loaded: dlopen(/usr/local/mysql/lib/plugin/mysql_old_password.so, 2): image not found")
或者看到差不多的信息,比如:
OperationalError: (2059, "Authentication plugin 'mysql_old_password' couldn't be found in restricted_auth plugin list.")
这错误信息直白地告诉你:老哥,你要用的 mysql_old_password
这个认证插件,我要么是找不到对应的 .so
文件(动态链接库),要么就是我本地的 MySQL 客户端库(libmysqlclient)配置不允许加载这种不安全的旧插件。反正,结果就是连接失败。
别慌,虽然不能动服务器,咱们在客户端这边还是有不少办法可以折腾一下的。
一、 问题根源在哪?
这事的根本原因在于 MySQL 的认证机制迭代。
mysql_old_password
: 这是 MySQL 早期版本(比如 4.1 之前)使用的密码哈希算法。它非常不安全,容易被破解。因为安全风险太大,早就被官方废弃了。mysql_native_password
: 在 MySQL 4.1 后引入,直到 8.0 之前是默认的认证方式,安全性比mysql_old_password
好得多。caching_sha2_password
: MySQL 8.0 开始引入并作为默认,安全性更强。
你的问题场景是:
- 目标数据库服务器:很老,用户账号还在用
mysql_old_password
。 - 你的 Python 环境:安装了
MySQLdb
或mysqlclient
。 - 底层的 MySQL C 连接库 (
libmysqlclient
):你系统里安装的这个库可能比较新,出于安全考虑,它默认不再支持(或者找不到实现mysql_old_password
的插件文件)这种过时且不安全的认证方式了。错误信息里的dlopen
失败或者restricted_auth plugin list
就是在抱怨这事儿。
说白了,就是新版客户端嫌弃老服务器的认证方式太土、太危险,不愿意(或找不到工具)配合了。
二、 客户端解决方案
既然不能改服务端,我们就在客户端这边想办法。思路主要有两个:要么让客户端“屈尊”去兼容老的认证方式,要么换个“能屈能伸”的客户端库。
方案一: 降级或切换 MySQL C 连接库 (libmysqlclient
)
错误信息明确指向了动态链接库 (.so
文件) 加载失败。这通常意味着你系统里安装的 libmysqlclient
(或者 libmysqlclient-dev
,如果你是从源码编译 MySQLdb
的话)版本太新,不再自带或允许加载 mysql_old_password
插件。
原理:
找到一个与 MySQL 5.5.x 时期匹配的、并且包含 mysql_old_password
插件支持的 libmysqlclient
版本,替换掉你系统里当前的库。这样,当 MySQLdb
调用 C 库进行连接时,就能找到并加载所需的旧认证插件。
操作步骤:
这个操作有点硬核,并且可能影响系统上其他依赖 libmysqlclient
的程序,需要小心。
-
确认当前库版本:
- 在 Linux 上,你可能需要查找类似
libmysqlclient.so.X
的文件(X 是版本号,如 18, 20, 21),可以用ldconfig -p | grep libmysqlclient
或者dpkg -l | grep libmysqlclient
(Debian/Ubuntu) 或rpm -qa | grep mysql-community-libs
(CentOS/RHEL) 来查找。 - 在 macOS 上,可能通过
brew info mysql-client
(如果用 Homebrew 安装) 查看,或者在/usr/local/mysql/lib
等路径下查找。
- 在 Linux 上,你可能需要查找类似
-
卸载当前库(或者小心处理):
- 风险提示: 直接卸载系统级的库可能会破坏依赖它的其他应用!请务必谨慎。
- Debian/Ubuntu:
sudo apt-get remove libmysqlclient-dev libmysqlclientXX
(XX 是版本号) - CentOS/RHEL:
sudo yum remove mysql-community-libs mysql-community-devel
- macOS (Homebrew):
brew uninstall mysql-client
-
寻找并安装旧版本库:
- 官方源: 尝试通过包管理器寻找旧版本的包。例如,在 Ubuntu 上可能有
libmysqlclient18
(对应 MySQL 5.5/5.6 时期)。sudo apt-get install libmysqlclient18 libmysqlclient-dev=版本号
(可能需要指定确切版本)。 - 第三方仓库或 PPA: 某些旧软件仓库可能还保留着老版本的库。
- 手动下载编译: 去 MySQL 官网的归档下载页面 (Archive),找到 MySQL Community Server 5.5.x 的源码包,尝试只编译其
libmysqlclient
部分。这个过程相当复杂,需要处理编译依赖和配置。 - 从旧系统提取: 如果你有运行相似旧版本 Linux 或 macOS 的虚拟机/容器,可以考虑从那里复制
libmysqlclient.so.X
文件和相关的插件文件(比如mysql_old_password.so
),但这非常不推荐,容易出兼容性问题。
- 官方源: 尝试通过包管理器寻找旧版本的包。例如,在 Ubuntu 上可能有
-
重新安装
MySQLdb
:
如果MySQLdb
是源码编译安装的,在切换了libmysqlclient
后,可能需要重新编译安装MySQLdb
,确保它链接到正确的库。pip uninstall MySQL-python # 确保你的环境能找到旧版的 mysql_config 或 C Header/Library 路径 pip install MySQL-python
安全建议:
- 极其不推荐! 这是最后的手段。使用支持
mysql_old_password
的老旧 C 库意味着你可能引入了已知的安全漏洞。 - 隔离环境: 如果非要这么做,强烈建议在 Docker 容器或独立的虚拟环境中进行,避免污染主系统。选择一个自带老版本
libmysqlclient
的基础镜像(比如一个老的 CentOS 或 Ubuntu 镜像)可能会简单点。
进阶技巧:
- 如果你选择编译,可以查看 MySQL 源码编译时的 CMake 选项,看是否有强制启用或包含旧插件的开关。
- 使用
ldd
(Linux) 或otool -L
(macOS) 命令检查_mysql.so
(MySQLdb
编译出的 C 扩展模块) 实际链接到了哪个libmysqlclient
库,确保是你安装的旧版本。
方案二: 换用纯 Python 实现的数据库连接库 (PyMySQL
)
MySQLdb
(以及它的现代分支 mysqlclient
) 是 C 扩展库,它依赖系统底层的 C 库 libmysqlclient
。而 PyMySQL
是一个纯 Python 实现的 MySQL 连接库,它不依赖外部的 C 库,自己实现了 MySQL 客户端协议,包括认证部分。
原理:
PyMySQL
在自己的 Python 代码里实现了与 MySQL 服务器通信的协议细节。因为它不依赖 libmysqlclient
,所以也就绕开了 C 库找不到或拒绝加载 mysql_old_password.so
的问题。如果 PyMySQL
的开发者实现了对 mysql_old_password
的支持(或者提供了某种兼容方式),那它就能连接。好消息是,PyMySQL
通常对旧认证方式有更好的兼容性。
操作步骤:
-
安装
PyMySQL
:pip install PyMySQL
如果你还在用 Python 2.7,请确保你的 pip 是最新的,并且安装的是兼容 Python 2.7 的
PyMySQL
版本 (PyMySQL 最后支持 Python 2.7 的版本是 0.10.x 系列)。 -
修改 Python 代码:
将你的数据库连接代码从MySQLdb
切换到PyMySQL
。API 非常相似,改动通常很小。原来的
MySQLdb
代码可能像这样:import MySQLdb try: db = MySQLdb.connect(host='your_host', user='your_user', passwd='your_password', db='your_database', port=3306) # 端口号可选 # ... 使用 db 对象操作数据库 ... db.close() except MySQLdb.OperationalError as e: print("MySQL 连接失败: %s" % (e,))
修改为
PyMySQL
:import pymysql # 注意导入名字变了 try: db = pymysql.connect(host='your_host', user='your_user', password='your_password', # 注意参数名是 password 不是 passwd database='your_database', # 参数名是 database 不是 db port=3306, charset='utf8mb4', # 建议明确指定字符集 cursorclass=pymysql.cursors.DictCursor) # 可选,让查询结果返回字典形式 # ... 使用 db 对象操作数据库 ... # 获取游标的方式稍有不同,但用法基本一致 with db.cursor() as cursor: cursor.execute("SELECT VERSION()") version = cursor.fetchone() print("Database version: %s" % version) db.close() except pymysql.OperationalError as e: print("PyMySQL 连接失败: %s" % (e,)) # PyMySQL 可能抛出更具体的错误代码,可以根据需要捕获处理 # 例如: error_code, error_message = e.args
为什么这个方案通常更好?
- 不依赖系统环境: 纯 Python 实现,不依赖外部 C 库,避免了
libmysqlclient
版本和配置带来的麻烦。环境部署更简单。 - 更好的兼容性(可能):
PyMySQL
社区可能更有意愿去维护对旧版协议的兼容性。 - 与现代 Python 更兼容:
PyMySQL
对 Python 3 支持更好,如果以后需要升级 Python 版本,迁移会更容易。
安全建议:
- 虽然
PyMySQL
可能帮你连上了使用mysql_old_password
的数据库,但这并没有改变mysql_old_password
本身不安全的事实。传输的密码哈希依然容易被窃听和破解。 - 强烈建议与数据库管理员沟通,说明安全风险,争取将用户账号升级到
mysql_native_password
或更高。这是最根本、最安全的解决之道。
进阶技巧:
- 查阅
PyMySQL
的文档,看是否有特定的连接参数可以用来显式处理旧认证或调整安全策略(比如ssl
参数强制加密传输,虽然不能解决认证本身的弱点,但能保护传输过程)。 - 注意
PyMySQL
和MySQLdb
在某些细节上(如参数名、错误处理、游标类型)可能存在细微差异,迁移时需要测试。
方案三: 使用支持旧认证的 Connector/Python (官方驱动)
MySQL 官方也提供了 Python 连接器 mysql-connector-python
。它有纯 Python 实现的版本,也有 C 扩展版本。
原理:
类似于 PyMySQL
,官方连接器的纯 Python 版本不依赖 libmysqlclient
,可能内置了对 mysql_old_password
的支持。你需要确保安装的是纯 Python 版本,或者 C 扩展版本所依赖的库兼容。
操作步骤:
-
安装
mysql-connector-python
:# 尝试安装纯 Python 版本 (可能需要指定,或者默认就是) pip install mysql-connector-python # 如果遇到问题,可能需要确保没有安装 C 扩展需要的依赖,迫使其回退到纯 Python # 或者查找是否有指定纯 Python 安装的选项
-
修改代码: API 与
MySQLdb
和PyMySQL
不同。import mysql.connector try: cnx = mysql.connector.connect(user='your_user', password='your_password', host='your_host', database='your_database', port=3306, auth_plugin='mysql_old_password') # 关键点:尝试显式指定认证插件 # ... 使用 cnx 操作 ... cursor = cnx.cursor() cursor.execute("SELECT VERSION()") # ... cursor.close() cnx.close() except mysql.connector.Error as err: if err.errno == mysql.connector.errorcode.ER_ACCESS_DENIED_ERROR: print("用户名或密码错误") elif err.errno == mysql.connector.errorcode.ER_BAD_DB_ERROR: print("数据库不存在") else: print(err)
注意事项:
mysql-connector-python
的auth_plugin
参数是这里的关键。你需要查阅你所用版本的文档,确认是否支持设置为'mysql_old_password'
以及如何正确使用。- 官方驱动有时更新较快,对旧版协议的支持策略可能会变化。安装时需注意版本。
- 性能上,纯 Python 的连接库通常比 C 扩展的要慢一些,但对于很多应用场景来说,这种差异可以接受。
安全建议:
同样,即使能连上,安全风险依旧。指定 auth_plugin='mysql_old_password'
只是告诉连接器使用不安全的方法,并没有让它变安全。
总结一下
遇到 Python 连接老旧 MySQL 因 mysql_old_password
认证失败,又不能改服务器配置时,客户端这边主要思路是:
- 硬核方案(不推荐): 更换系统底层的 MySQL C 客户端库 (
libmysqlclient
) 到一个支持旧认证的古老版本。风险高,操作复杂,可能影响系统稳定性,还有安全隐患。 - 推荐方案: 换用纯 Python 实现的 MySQL 连接库,比如
PyMySQL
。它不依赖系统 C 库,通常对旧认证兼容性更好,安装和使用也相对简单。这是解决这类问题的常用方法。 - 备选方案: 尝试 MySQL 官方的
mysql-connector-python
,特别是它的纯 Python 实现,并尝试显式指定auth_plugin='mysql_old_password'
参数。
无论选择哪种方案成功连接,都请牢记:mysql_old_password
是个严重的安全隐患。连接成功只是第一步,长远来看,推动数据库管理员升级认证方式才是治本之道。