返回

Python连接旧版MySQL:轻松解决mysql_old_password认证错误

mysql

搞定 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 的认证机制迭代。

  1. mysql_old_password 这是 MySQL 早期版本(比如 4.1 之前)使用的密码哈希算法。它非常不安全,容易被破解。因为安全风险太大,早就被官方废弃了。
  2. mysql_native_password 在 MySQL 4.1 后引入,直到 8.0 之前是默认的认证方式,安全性比 mysql_old_password 好得多。
  3. caching_sha2_password MySQL 8.0 开始引入并作为默认,安全性更强。

你的问题场景是:

  • 目标数据库服务器:很老,用户账号还在用 mysql_old_password
  • 你的 Python 环境:安装了 MySQLdbmysqlclient
  • 底层的 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 的程序,需要小心。

  1. 确认当前库版本:

    • 在 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 等路径下查找。
  2. 卸载当前库(或者小心处理):

    • 风险提示: 直接卸载系统级的库可能会破坏依赖它的其他应用!请务必谨慎。
    • 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
  3. 寻找并安装旧版本库:

    • 官方源: 尝试通过包管理器寻找旧版本的包。例如,在 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),但这非常不推荐,容易出兼容性问题。
  4. 重新安装 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 通常对旧认证方式有更好的兼容性。

操作步骤:

  1. 安装 PyMySQL

    pip install PyMySQL
    

    如果你还在用 Python 2.7,请确保你的 pip 是最新的,并且安装的是兼容 Python 2.7 的 PyMySQL 版本 (PyMySQL 最后支持 Python 2.7 的版本是 0.10.x 系列)。

  2. 修改 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 参数强制加密传输,虽然不能解决认证本身的弱点,但能保护传输过程)。
  • 注意 PyMySQLMySQLdb 在某些细节上(如参数名、错误处理、游标类型)可能存在细微差异,迁移时需要测试。

方案三: 使用支持旧认证的 Connector/Python (官方驱动)

MySQL 官方也提供了 Python 连接器 mysql-connector-python。它有纯 Python 实现的版本,也有 C 扩展版本。

原理:

类似于 PyMySQL,官方连接器的纯 Python 版本不依赖 libmysqlclient,可能内置了对 mysql_old_password 的支持。你需要确保安装的是纯 Python 版本,或者 C 扩展版本所依赖的库兼容。

操作步骤:

  1. 安装 mysql-connector-python

    # 尝试安装纯 Python 版本 (可能需要指定,或者默认就是)
    pip install mysql-connector-python 
    # 如果遇到问题,可能需要确保没有安装 C 扩展需要的依赖,迫使其回退到纯 Python
    # 或者查找是否有指定纯 Python 安装的选项
    
  2. 修改代码: API 与 MySQLdbPyMySQL 不同。

    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-pythonauth_plugin 参数是这里的关键。你需要查阅你所用版本的文档,确认是否支持设置为 'mysql_old_password' 以及如何正确使用。
  • 官方驱动有时更新较快,对旧版协议的支持策略可能会变化。安装时需注意版本。
  • 性能上,纯 Python 的连接库通常比 C 扩展的要慢一些,但对于很多应用场景来说,这种差异可以接受。

安全建议:

同样,即使能连上,安全风险依旧。指定 auth_plugin='mysql_old_password' 只是告诉连接器使用不安全的方法,并没有让它变安全。

总结一下

遇到 Python 连接老旧 MySQL 因 mysql_old_password 认证失败,又不能改服务器配置时,客户端这边主要思路是:

  1. 硬核方案(不推荐): 更换系统底层的 MySQL C 客户端库 (libmysqlclient) 到一个支持旧认证的古老版本。风险高,操作复杂,可能影响系统稳定性,还有安全隐患。
  2. 推荐方案: 换用纯 Python 实现的 MySQL 连接库,比如 PyMySQL。它不依赖系统 C 库,通常对旧认证兼容性更好,安装和使用也相对简单。这是解决这类问题的常用方法。
  3. 备选方案: 尝试 MySQL 官方的 mysql-connector-python,特别是它的纯 Python 实现,并尝试显式指定 auth_plugin='mysql_old_password' 参数。

无论选择哪种方案成功连接,都请牢记:mysql_old_password 是个严重的安全隐患。连接成功只是第一步,长远来看,推动数据库管理员升级认证方式才是治本之道。