我正在尝试从具有UnixODBC和pyodbc 3.07的Ubuntu 16客户端连接到旧的MySQL 3.23服务器。我已经尝试了三 (3) 个版本的 MySQL 连接器/ODBC 和两 (2) 个来自 MariaDB 的版本:
MySQL-ODBC 5.3.9仅支持新的 mysql 身份验证方法。因此它无法连接。
MySQL-ODBC 5.1.13有一个用于身份验证方法的开关,但在pyodbc.connect(dsn)
告诉我:[MySQL][ODBC 5.1 Driver]Driver does not support server versions under 4.1.1
MySQL-ODBC 3.51有两个问题:
- 失败并显示
[MySQL][ODBC 3.51 Driver]Transactions are not enabled (4000) (SQLSetConnnectAttr(SQL_ATTR_AUTOCOMMIT))
,因为 pyodbc 将自动提交设置为 false 作为默认值。 - 当我与
pyodbc.connect(dsn, autocommit=True)
连接时给我一个连接。连接给了我一个游标,但所有游标.execute(sql)都抛出异常('HY000', 'The driver did not supply an error!')
。
通过isql -v [dsn]
从 shell 测试与 isql 的连接给了我一个会话,但所有带有[ISQL]ERROR: Could not SQLExecute
的语句都失败了。所以这似乎是一个 unixodbc 问题。
我安装了mysql-client。但是程序mysql无法连接服务器。
mariadb-client可以连接到数据库,甚至可以执行语句。这看起来更有希望。
我下载了MariaDB ODBC-Driver 3.0.2。将该驱动程序与 isql 一起使用将返回错误:[S1000][unixODBC][ma-3.0.2]Plugin old_password could not be loaded: lib/mariadb/plugin/old_password.so: cannot open shared object file: No such file or directory
。这是人们可以处理的回应。有一个 ODBC 选项PLUGIN_DIR但我不知道从哪里获得插件。
MariaDB ODBC-Driver 2.0.13为我提供了连接('HY000', "[HY000] [unixODBC][ma-2.0.13]You have an error in your SQL syntax near 'SQL_AUTO_IS_NULL=0' at line 1 (1064) (SQLDriverConnect)")
。因为似乎没有改变这一点的选择。死胡同在这里。
我想知道是否有办法通过unixodbc/pyodbc访问这个旧的MySql?
或者有人知道在哪里可以获得MariaDB的插件old_password.so?
通过apt-get安装的mariadb-client可以连接,所以必须有办法。
我花了一天左右的时间研究这个问题,如果不对驱动程序代码进行重大更改,或者为旧版本创建极其困难的构建环境,就不可能做到这一点。
我把这个放在一个答案中,这样其他人就不会掉进我做的同样的兔子洞(或者,更好的是,这样其他人就可以从我离开的地方继续并实际解决问题!它不适合评论。
这将有点大部头,对不起。
概述
我能够重现您在帖子中提到的每个错误条件(感谢您提出一个彻底而出色的问题!)使用一对 Ubuntu 16.04 容器、可从 Oracle 下载的 MySQL 3.23 以及您提到的所有客户端库以及其他一些
。以下是我在尝试在您提到的每个地方找到其他解决方案时发现的内容,然后是一些"后续步骤"类型的信息和一些关于故事寓意的劝说。
所有这些测试都是使用最新版本的Python 2,UnixODBC和pyodbc
(通过pip
)进行的,截至2017年11月26日可用于库存的Ubuntu 16.04 Docker容器。
所有使用的URL都是链接的,但是,如果历史有任何迹象,考虑到很多软件已经接近二十年的历史,它们可能会随着时间的推移而死亡。如果您愿意,我也很乐意发布我的任何/所有 shellscript/Docker文件/修改的驱动程序源;只是在评论中通知我。
old_password.so 和 MariaDB Connector/ODBC 3.0.2
您是对的,这是最具潜力的故障排除选项。这是我所做的:
首先,我安装了 Connector/ODBC 3.0.2 二进制文件,并尝试通过 Python 连接到它。我在为名为"maria"的数据源配置 ODBC.ini
文件后遇到了相同的错误,即:
> pyodbc.connect('DRIVER={maria};Server=mysql;Database=mysql;User=admin;Password=admin')
pyodbc.Error: ('HY000', u'[HY000] [unixODBC][ma-3.0.2]Plugin old_password could not be loaded: lib/mariadb/plugin/old_password.so: cannot open shared object file: No such file or directory (2059) (SQLDriverConnect)')
当 MySQL 服务器宣布足够旧的身份验证协议时,ODBC 代码会尝试加载为 Connector/C MariaDB 驱动程序构建的编译插件。strace
ODBC 连接尝试的输出确定了这一点。
old_password.so
原来是连接器/C MariaDB驱动程序的组件,但不是该驱动程序的二进制版本中包含的库。有趣。
事实证明,有一堆类似于连接器/C 驱动程序源中包含的old_password
插件模块。我下载了 Connector/C 3.0.2 源代码,并为那些作为.so
文件分发的"auth"类型插件打开了文档、源代码和构建系统,看看我能找到什么。
我发现 Connector/C 的各种组件既可以编译为"静态"链接到主驱动程序库的插件,也可以编译为动态库本身。我在引号中说"静态",因为 C 驱动程序的构建过程会同时创建静态 (.a
) 和动态 (.so
) 版本的mariadbclient
,但如果特定插件在构建系统中声明为静态,则该插件的代码静态包含在mariadbclient
工件中。
old_password.so
文件的源似乎在plugins/auth/old_password.c
的单个小源文件中。
似乎可以更改构建系统(CMake)来为old_password
插件生成动态库。在连接器/C源代码中,有一个cmake/plugins.cmake
文件,它充当所有插件的"注册表"。它包含一个 cmake 宏REGISTER_PLUGIN
,它采用STATIC
或DYNAMIC
参数。我在该文件中搜索了old_password
,并找到了以下行:
REGISTER_PLUGIN("AUTH_OLDPASSWORD" "${CC_SOURCE_DIR}/plugins/auth/old_password.c" "old_password_client_plugin" "STATIC" "" 0)
这看起来很有希望。根据确实为其插件生成.so
文件的类似行进行建模,我将该行更改为以下内容并运行构建:
REGISTER_PLUGIN("AUTH_OLDPASSWORD" "${CC_SOURCE_DIR}/plugins/auth/old_password.c" "old_password_client_plugin" "DYNAMIC" "old_password" 1)
由于缺少依赖项,生成失败了几次。我不得不安装一些-dev
软件包和其他工具,但最终我能够干净地构建(仅对于插件,事实证明您不需要 CURL 或 OpenSSL)。果然,在plugins/auth
目录中创建了一个名为mysql_old_password.so
的文件作为构建工件。 - 现在,我需要我的 Python 代码来找到那个插件;它仍然给了我找不到lib/mariadb/plugin/old_password.so
的错误。我将您在问题中提到的PLUGIN_DIR
参数提供给 ODBC 连接字符串,将我的编译mysql_old_password.so
重命名为old_password.so
,并运行以下代码。并得到一个新错误!进展!
conn = pyodbc.connect('DRIVER={maria};Server=mysql;Database=mysql;User=admin;Password=admin;PLUGIN_DIR=/home/mysql/zclient/mdb-c/plugins/auth')
pyodbc.Error: ('HY000', u'[HY000] [unixODBC][ma-3.0.2]Plugin old_password could not be loaded: /home/mysql/zclient/mdb-c/plugins/auth/old_password.so: undefined symbol: ma_scramble_323 (2059) (SQLDriverConnect)')
看起来编译的项目已损坏,缺少ma_scramble_323
函数定义。由于插件是在运行时动态加载的,因此程序仍将启动,但是当它尝试dload
插件时,它会爆炸。更糟糕的是,该函数看起来像是"旧"MySQL协议身份验证机制的主要密码哈希入口点,所以我不能放弃它。在连接器/C源中,我找到了该函数和标头(mariadb_com.h
)的声明,但是include
在old_password.c
源文件的不同位置似乎没有解决问题。我的预感是,这是两种不幸行为的相互作用。首先,设置由连接器/C 构建系统编译的插件,假设它们仅由连接器/C 插件或类似的东西链接。这意味着插件本身在编译时不会链接到"通用"连接器/C 功能,因为这些东西应该已经在加载插件的东西中可用。由于我们使用的是连接器/ODBC,而不是连接器/C,因此这些常用函数不存在或无法访问。现在,从源代码构建连接器/ODBC 需要 Connector/C,因此 IIS 可以编译一个新的连接器/ODBC 库,使其包含正确的函数,但我不想从那个兔子洞开始。其次,即使被告知在独立(不要编译其他任何东西)模式下构建old_password
插件,CMake 的依赖分析也没有发现或链接描述ma_scramble_323
的文件。这可能是一个 CMake 问题,但可能是因为构建系统没有如上所述考虑此用例。
在这里,我非常幸运。ma_scramble_323
函数在libmariadb/ma_password.c
中定义,这是一个非常小、简单的源文件,对 Connector/C 项目中尚未被old_password
插件依赖的任何其他库没有明显的依赖关系。我做了"穷人的链接"(哎呀),只是将ma_scramble_323
函数的源代码复制到old_password.c
文件中。这些函数调用了ma_password.c
文件中的其他函数,所以我将它们复制到了。同样,这只是简单的(或者根本不是一个选项),因为ma_password.c
文件非常简单。如果它本身有依赖关系或更复杂,我将不得不停下来,放弃并学习高级CMake-fu以"正确"的方式解决问题。我绝对确信有更好的方法来做到这一点。
(旁白)在这一点上,我不得不在我的数据库服务器上定期运行mysqladmin flush-hosts
,因为我的测试导致如此多的失败尝试,以至于我不得不经常这样做。可能也有更好的解决方法,但我不知道,我知道 cron。
使用新的"内联"源代码,编译了mysql_old_password.so
库,我重命名了它,并再次运行了我的测试脚本。这一次,我得到了:
pyodbc.Error: ('HY000', u'[HY000] [unixODBC][ma-3.0.2]Plugin old_password could not be loaded: name mismatch (2059) (SQLDriverConnect)')
我认为这与我重命名文件以便 ODBC 可以找到它的事实有关(它正在寻找old_password.so
而不是mysql_old_password.so
)。我尝试了霰弹枪方法。在plugins/auth/CMakeLists.txt
构建系统配置中,我用old_password
替换了所有mysql_old_password
实例并进行编译。编译成功,但仍然不起作用。
事实证明,插件源本身(在这种情况下old_password.c
)在顶部有一个结构声明,宣布它们的名称,而这个声明将其名称宣布为mysql_old_password
.这很可能是一个预先存在的问题(即这从未奏效),我开始感到有点不寒而栗:当你构建的代码感觉以前没有人在给定的配置中构建或测试它时,你的成功几率并不大。无论如何,我也对源文件做了同样的s/mysql_old_password/old_password/
,并进行了编译。这一次,它生成了一个具有正确old_password.so
名称的工件。我再次运行测试脚本并得到:
conn = pyodbc.connect('DRIVER={maria};Server=mysql;Database=mysql;User=admin;Password=admin;PLUGIN_DIR=/home/mysql/zclient/mdb-c/plugins/auth')
pyodbc.Error: ('HY000', u"[HY000] [unixODBC][ma-3.0.2]Access denied for user: 'admin@hostname' (Using password: NO) (1045) (SQLDriverConnect)")
这很奇怪。我的客户端测试盒上也安装了 3.23 服务器附带的mysql
命令行客户端(通过 tarball,而不是在系统库路径中),它可以与这些凭据很好地连接(我无法使用isql
进行测试,因为我无法让它正确使用PLUGIN_DIR
并且无法弄清楚它希望我在哪里放置插件; 它不在系统/usr
目录中, 也不是相对的)。我想不出办法解决这个问题。我已经用所有常见的"超混杂,仅测试"GRANT
设置了我的MySQL服务器,用于localhost
和%
,每个数据库,admin
用户和同名密码。
我放弃并将密码设置为空/空,禁用密码身份验证,确保我仍然可以通过命令行上的mysql
登录,并最后一次尝试:
pyodbc.Error: ('HY000', u'[HY000] [unixODBC][ma-3.0.2]Error in server handshake (2012) (SQLDriverConnect)')
这被证明是丧钟。研究这个错误,我发现了这个 GitHub 问题,其中人们似乎非常确信这代表了基本的客户端/服务器协议不兼容。在这一点上,我放弃了old_password.so
的方法。似乎 MariaDB 驱动程序代码(C 或 ODBC)的 3.0.2 版本没有说足够旧的 MySQL 协议方言来工作,尽管在此过程中我可能错过了很多可能的修复程序。
尝试过的其他路径
我尝试了您在问题中提到的其他一些事情,我将在这里简要介绍:
您可能发现,尝试禁用 MariaDB 2.0 ODBC 驱动程序系列中的
SQL_AUTO_IS_NULL
行为效果不佳。此 bug 线程和 ODBC 连接器参数列表提供了一些有关如何禁用该字段设置的建议(Option=8388608
显而易见且清晰,对吧?),但这些强制禁用或启用标志的尝试都没有更改行为,无论它们是在连接字符串中还是在 ODBC.ini
文件中。MySQL 存档站点具有旧版本的 ODBC 连接器可用。不幸的是,他们所有的编译版本都是针对 32 位 Linux 的,而我没有。我尝试从源代码构建,即使配置工具链也是一件艰巨的工作。在我不得不手动安装1999年的系统标识文件时,我知道这可能是一个失败的原因,但是我安装了所有deps和旧版本并尝试编译它。编译错误的数量和种类之多导致我放弃了这种方法(C 标准不匹配,加上缺乏与 UnixODBC 几乎每个部分的兼容性)。我错过的这些问题完全有可能有简单的解决方法;我不是C程序员或旧的linux构建系统专家。
我尝试了一些第三方MySQL ODBC连接器,但不起作用;与5.*系列相同的错误。
我编译了连接器/ODBC 库的 2.50.39 版本(存档中只有源代码可用)。为此,我首先编译了 3.23 版服务器的
libmysqlclient.so.10
文件。这需要改变 3.23 服务器的源代码来解决一些与errno
相关的问题(删除my_sys.h
中extern int errno
的#define
子句),将 libtool OS 定义文件复制到源目录中的不同位置(/usr/share/libtool/build-aux/config.{guess,sub}
被复制到.
、mit-pthreads
和mit-pthreads/config/
,如果重要的话)。之后,我能够使用--with-mit-threads --without-server --without-docs --without-bench
配置交换机来编译和构建libmysqlclient
库。在那之后评估mysql
客户端程序的宏时,编译失败并出现了几个难以理解的错误,但是libmysqlclient
的.so
文件已经生成,所以我抓住它们并继续前进。编译libmysqlclient.so.10
库后,我从存档中构建了 2.50.39 版本的连接器/ODBC。这需要从主MySQL包含文件中更改一些源代码(删除对asm/atomic.h
的引用),以及与其他库相同的系统标识libtool黑客。它无法找到iodbc
库(通过libiodbc2-dev
包安装在 Ubuntu 上),因为它们现在处于/usr/include
而不是/usr/local/include
中。我终于为它配置了 开关--with-mysql-includes=$path_to_3.23_mysql_binary_dir/include --with-mysql-libs=$path_to_compiled_libmysqlclient.so.10_files_from_mysql_server_3.23_sources --with-iodbc-includes=/usr/include/iodbc
,除了上述atomic.h
问题外,它的构建没有遇到任何问题。但是,毕竟,通过我新编译的libmyodbc.so
进行连接会导致Python/UnixODBC中的段错误。瓦尔格林德、gdb
和其他工具在确定原因方面没有用;也许更精通调试编译库互操作性问题的人可以解决这个问题。MySQL 存档具有旧的二进制 RPM 版本的连接器/ODBC。它们都是 32 位的,几乎所有现代 Linux 都是 64 位的。我尝试通过安装
i386
架构和所需的库来填充这些文件。64 位 Python/UnixODBC 无法成功加载myodbc
插件,返回了通用的"找不到文件"错误,我最终将其追溯到对dlopen
的失败调用。Libtool的dlopen
包装器(由UnixODBC使用)被大多数人认为不是很可调试,在经历了一个很大的麻烦之后,我基本的Valgrind技巧似乎表明,正如我所期望的那样,不可能动态加载一个架构不兼容(i386
与x86-64
)的ODBC后端。
解决方案/剩余选项
一般来说,重写代码可能会更容易。例如,你可以创建一个Python模块,它包装了一个遗留的Python非ODBC MySQL驱动程序(正如@FlipperPA在关于这个问题的评论中所建议的那样),将pyodbc
接口的"足够"破解到该模块上,你不必重构太多调用它的代码,并在部署之前进行彻底测试。我知道这很糟糕,而且有风险,但这可能是你最好的选择。在编写此类模块时,您可以使用pyodbc
中的一些内部代码来处理泛型 ODBC 语法/等。
你甚至可以为pyodbc
开发一个"假"的ODBC后端,它只是调用一个非ODBC Python MySQL驱动程序,但我怀疑这会很困难,因为pyodbc
的后端可插拔性似乎主要面向编译库而不是"虚拟"填充码。
我不是这方面的专家,所以完全可能有一些我错过的解决方案!
我还考虑过并放弃了其他一些可能性:
您可以向MariaDB人员提交错误,并且可能会得到修复。我不太清楚我最终得到的协议错误是"这在每个级别上都从根本上不兼容"还是"身份验证系统只需要调整,然后一切都会正常工作"。可能值得一试。
由于有 32 位 RPM 可用于版本 2.50 连接器/ODBC 代码(它们不会加载到 64 位 Python/UnixODBC 环境中),因此您可以想象将整个堆栈(甚至操作系统发行版)转换为 32 位代码。但是,如果您使用任何不常见的编译内容,这可能会非常麻烦。虽然 Ubuntu/Debian 特别擅长在旧架构上提供软件包,但这仍然可能很棘手。即使您转换了所有内容,某些行为也可能会改变,对于任何在您的应用程序上工作的人来说,旧的 32 位特征都将是一个持续的陌生之处。仅当 2.50 驱动程序在从 32 位运行时访问时正常工作时,这才有效;之后可能会出现其他问题。我只建议在将来所有客户端代码的维护负担可能非常低的情况下尝试此操作(如果项目很小或不太可能更改)。
故事的寓意
软件腐烂得很快。除非一个项目不断致力于保持向后兼容性,否则事情很快就会停止工作,尤其是在Web软件中。
不是产品本身坏了,而是宇宙以一百万种小的方式从它下面变化出来。除非有人足够多才,并且已经存在足够长的时间知道所有这些小变化以及如何扭转它们,否则很难将所有时间/修订都倒退到一个事情"正常工作"的地方。
如果你得到某些东西的二进制文件,即使它是像MySQL驱动程序这样被认为是"常见"的东西,也要保留它们。理想情况下,与互联网共享它们。
如果你有某物的来源,严格记录他们需要的整个依赖项/工具链列表,并为人类记录下来。假设读取程序化依赖项列表(例如自动工具)所需的工具本身将过时。没有什么是太"明显"而无法记录的;不是架构,内核ABI,libc行为 - 什么都没有。现在我们有了像 Docker 这样"在任何内核上的盒子里"的东西,你可以以编程方式存储更多的依赖项,但不要指望它。
正如Zac B所展示的那样,没有真正的方法可以将Ubuntu 16客户端的MySQL 3.23服务器与UnixODBC和pyodbc 3.07连接起来。
FlipperPA建议使用 pypi.python.org/pypi/MySQL-python/1.2.4 作为下一个可能的解决方案。所以我试了一下:
通过apt安装的MySQL-python无法连接到旧的MySQL。它使用不适用于MySQL 3.23的系统mysql-client库。
通过pip安装的MySQL-python是另一回事:它从libmysqlclient-dev包中包含的mysql_config获取库。也不适用于MySQL 3.23。但是这里有机会修改一些东西。
当我安装libmariadb-client-lgpl-dev时,我得到的mariadb_config看起来很像mysql_config。Ubuntu 16 的 mariadb-client 使用 MySQL 3.23(如上所示)。
ln -s /usr/bin/mariadb_config mysql_config
pip install MySQL-python
这完成了工作。我可以从Python连接旧的MySQL服务器。
数据类型存在一些问题,但在这种情况下我并不挑剔。