RHEL下sqlalchemy用mysql+mysqldb连接MySQL报Access denied,其他系统正常
感谢您花时间阅读此问题。我花费了大量时间查找解决方案,也翻阅了pydobc和mysqldb的源码,但始终没有找到原因。
问题概述
我可以使用相同的连接方式和连接串在多台机器上连接MySQL服务器,但在RHEL服务器上使用相同方法会持续抛出Access denied错误,目前基本排除是真实权限问题导致。
背景信息
我使用sqlalchemy连接部署在远程网络的MySQL服务器。使用相同的凭据,我可以通过MySQL Workbench、MySQL Shell、本地Jupyter Notebook、本地Python脚本、Ubuntu 18.04 WSL1虚拟机,以及出问题的这台RHEL服务器连接MySQL——但RHEL服务器无法使用其他实例通用的连接方式。
我确认我的用户拥有MySQL服务器对应库表的读写权限(而且我的笔记本也可以正常建立ODBC连接),所以尽管报错提示权限不足,我认为这不是真实原因,欢迎大家提供排查思路。
问题详情
除RHEL服务器外,在上述所有环境中我都可以使用以下代码创建引擎并建立连接:
engine = sqlalchemy.create_engine('mysql+mysqldb://{user}:{password}@{server}:{port}/{database}?charset=utf8mb4'.format(user=user ,password=password ,server=server ,port=port ,database=database)) con = engine.connect()
后续我可以正常使用该连接执行pd.to_sql()、直接执行SQL等操作,运行完全正常。
但在RHEL服务器上执行相同代码时,会抛出如下报错:
>>> engine.connect() Traceback (most recent call last): File "/python_dir/python3.8/site-packages/sqlalchemy/engine/base.py", line 3211, in _wrap_pool_connect return fn() File "/python_dir/python3.8/site-packages/sqlalchemy/pool/base.py", line 307, in connect return _ConnectionFairy._checkout(self) File "/python_dir/python3.8/site-packages/sqlalchemy/pool/base.py", line 767, in _checkout fairy = _ConnectionRecord.checkout(pool) File "/python_dir/python3.8/site-packages/sqlalchemy/pool/base.py", line 425, in checkout rec = pool._do_get() File "/python_dir/python3.8/site-packages/sqlalchemy/pool/impl.py", line 146, in _do_get self._dec_overflow() File "/python_dir/python3.8/site-packages/sqlalchemy/util/langhelpers.py", line 70, in __exit__ compat.raise_( File "/python_dir/python3.8/site-packages/sqlalchemy/util/compat.py", line 207, in raise_ raise exception File "/python_dir/python3.8/site-packages/sqlalchemy/pool/impl.py", line 143, in _do_get return self._create_connection() File "/python_dir/python3.8/site-packages/sqlalchemy/pool/base.py", line 253, in _create_connection return _ConnectionRecord(self) File "/python_dir/python3.8/site-packages/sqlalchemy/pool/base.py", line 368, in __init__ self.__connect() File "/python_dir/python3.8/site-packages/sqlalchemy/pool/base.py", line 611, in __connect pool.logger.debug("Error on connect(): %s", e) File "/python_dir/python3.8/site-packages/sqlalchemy/util/langhelpers.py", line 70, in __exit__ compat.raise_( File "/python_dir/python3.8/site-packages/sqlalchemy/util/compat.py", line 207, in raise_ raise exception File "/python_dir/python3.8/site-packages/sqlalchemy/pool/base.py", line 605, in __connect connection = pool._invoke_creator(self) File "/python_dir/python3.8/site-packages/sqlalchemy/engine/create.py", line 578, in connect return dialect.connect(*cargs, **cparams) File "/python_dir/python3.8/site-packages/sqlalchemy/engine/default.py", line 584, in connect return self.dbapi.connect(*cargs, **cparams) File "/python_dir/python3.8/site-packages/MySQLdb/__init__.py", line 130, in Connect return Connection(*args, **kwargs) File "/python_dir/python3.8/site-packages/MySQLdb/connections.py", line 187, in __init__ super().__init__(*args, **kwargs2) MySQLdb._exceptions.OperationalError: (1045, "Access denied for user 'USER'@'SERVER_ADDRESS' (using password: YES)")
出于安全考虑我对报错中的敏感信息做了修改,唯一的有效报错信息为:
MySQLdb._exceptions.OperationalError: (1045, "Access denied for user 'USER'@'SERVER_ADDRESS' (using password: YES)")
RHEL环境下的可用连接方式
尽管上述连接方式在RHEL上失效,但我可以通过另一种配置在RHEL上正常连接MySQL:
engine = sqlalchemy.create_engine('mysql+pyodbc://my_dsn')
该方式使用pyodbc,通过我在RHEL服务器/etc/odbc.ini中配置的DSN建立连接,对应配置示例如下(保留了原文件的大小写和空值,仅修改敏感信息):
[ODBC Data Sources] my_dsn = My Special DSN [my_dsn] Driver = /dir/libmyodbc5.so Description = Description SERVER = Server PORT = Port USER = User Password = Password Database = Database OPTION = 3 SOCKET =
我发现DSN配置和传给mysqldb的连接串之间唯一的显著差异是DSN可以指定OPTION参数,但我不清楚pyodbc如何使用该参数。
我查阅了sqlalchemy、pyodbc、MySQL官方文档、StackOverflow、SuperUser、相关源码,希望找到为mysqldb指定OPTION参数的方法,但始终没有结果。我不确定OPTION参数是否是导致问题的核心原因,但这是目前我唯一无法匹配的配置项。
我也找到了相关问答和对应的参考页面,对OPTION参数的含义有了基础了解,但仍未解决我的问题。
预期目标
我希望所有系统和服务器都可以使用完全相同的连接代码,不需要在每台机器上单独配置DSN(因此RHEL上使用pyodbc的方案不符合要求),但目前RHEL上没有其他可用的连接方式。
已尝试的排查操作
我对驱动和Linux配置的了解有限,不排除是我配置错误导致的问题,已尝试过以下操作:
- 尝试了多种不同的连接串,指定不同的驱动和连接参数
- 尝试在RHEL上安装多个不同的MySQL驱动,效果不一
- 查阅了数十篇同类问题的问答和技术文章
- 尝试了多种其他临时配置方案,均未生效
配置信息
- 问题服务器:RHEL Version 7
- 其他正常运行环境:Ubuntu 18.04、Windows 10
- Python版本:3.8
SQLAlchemy版本:RHEL为1.4.20,Windows为1.4.15,Ubuntu为1.4.22pyodbc版本:RHEL为4.0.30,Windows为4.0.30,Ubuntu为4.0.31
如果需要更多信息请随时告知,非常感谢大家的帮助。
解决方案
核心问题定位
你遇到的1045报错不是真实权限问题,是RHEL7默认的MySQL客户端配置、以及mysqlclient(即mysqldb底层依赖)的默认连接逻辑和ODBC配置的差异导致的。
你ODBC里配置的OPTION=3是两个标志位的组合:1(CLIENT_FOUND_ROWS)+2(CLIENT_LONG_PASSWORD),其中CLIENT_LONG_PASSWORD是解决问题的核心——RHEL7自带的系统级MySQL客户端库默认关闭了旧版密码哈希兼容逻辑,而你的MySQL服务端可能使用了旧版密码哈希算法存储用户密码,直接用mysqldb连接时不会自动启用该兼容逻辑,导致密码校验失败。
修复方案
直接在SQLAlchemy连接串的connect_args参数里传递对应的客户端标志即可,不需要修改任何系统配置,也能保证所有环境代码完全一致:
from MySQLdb.constants import CLIENT engine = sqlalchemy.create_engine( 'mysql+mysqldb://{user}:{password}@{server}:{port}/{database}?charset=utf8mb4'.format( user=user, password=password, server=server, port=port, database=database ), connect_args = { 'client_flag': CLIENT.FOUND_ROWS | CLIENT.LONG_PASSWORD } )
如果不想引入MySQLdb的依赖导入,也可以直接传递对应的数值(两个标志位的和为3,和你ODBC里的OPTION值完全一致):
engine = sqlalchemy.create_engine( 'mysql+mysqldb://{user}:{password}@{server}:{port}/{database}?charset=utf8mb4'.format( user=user, password=password, server=server, port=port, database=database ), connect_args = { 'client_flag': 3 } )
补充排查项
如果上述方案无效,可以再检查两个RHEL7特有的配置:
- 检查RHEL7的
/etc/my.cnf或/etc/mysql/my.cnf中是否有default-character-set相关配置,若默认字符集和其他环境不一致,也可能导致密码编码错误,你在连接串中显式指定charset=utf8mb4已经覆盖了该问题; - 检查RHEL7是否开启了SELinux强制访问控制,若开启可以临时执行
setenforce 0测试,确认是否是SELinux拦截了MySQL连接请求。
内容的提问来源于stack exchange,提问作者BowSto

