我有一个 Docker 容器,其中安装了 MySQL ODBC 驱动程序、unixODBC 和一堆 Python 东西。我的 MySQL 驱动程序通过
isql
工作,并且当我使用 pyodbc
从 Python 连接时也可以工作,如果我在新的 Python 进程中这样做的话:
sh-4.4# python
Python 3.8.16 (default, May 31 2023, 12:44:21)
[GCC 8.5.0 20210514 (Red Hat 8.5.0-18)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import pyodbc
>>> pyodbc.connect("DRIVER=MySQL ODBC 8.1 ANSI Driver;SERVER=host.docker.internal;PORT=3306;UID=root;PWD=shh")
<pyodbc.Connection object at 0x7f6fd94dac70>
但是,如果我在建立连接之前导入
pyarrow
,我会得到:
sh-4.4# python
Python 3.8.16 (default, May 31 2023, 12:44:21)
[GCC 8.5.0 20210514 (Red Hat 8.5.0-18)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import pyarrow
>>> import pyodbc
>>> pyodbc.connect("DRIVER=MySQL ODBC 8.1 ANSI Driver;SERVER=host.docker.internal;PORT=3306;UID=root;PWD=shh")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
pyodbc.Error: ('01000', "[01000] [unixODBC][Driver Manager]Can't open lib '/usr/lib64/libmyodbc8a.so' : file not found (0) (SQLDriverConnect)")
如果我直接指定驱动程序的路径,我会得到相同的结果:
sh-4.4# python
Python 3.8.16 (default, May 31 2023, 12:44:21)
[GCC 8.5.0 20210514 (Red Hat 8.5.0-18)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import pyarrow
>>> import pyodbc
>>> pyodbc.connect("DRIVER=/usr/lib64/libmyodbc8a.so;SERVER=host.docker.internal;PORT=3306;UID=root;PWD=shh")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
pyodbc.Error: ('01000', "[01000] [unixODBC][Driver Manager]Can't open lib '/usr/lib64/libmyodbc8a.so' : file not found (0) (SQLDriverConnect)")
过去,如果缺少库的传递依赖项,就会遇到来自 unixODBC 的相同/类似错误消息,我尝试这样做,试图看看是否有什么东西弄乱了加载器搜索路径。不确定这是否是一个有效的测试,但似乎没有什么问题:
>>> import os
>>> os.system('./lddtree.sh /usr/lib64/libmyodbc8a.so')
libmyodbc8a.so => /usr/lib64/libmyodbc8a.soreadelf: /usr/lib64/libmyodbc8a.so: Warning: Section '.interp' was not dumped because it does not exist!
(interpreter => none)
readelf: /usr/lib64/libmyodbc8a.so: Warning: Section '.interp' was not dumped because it does not exist!
libpthread.so.0 => /lib64/libpthread.so.0
libdl.so.2 => /lib64/libdl.so.2
libssl.so.1.1 => /lib64/libssl.so.1.1
libz.so.1 => /lib64/libz.so.1
libcrypto.so.1.1 => /lib64/libcrypto.so.1.1
libresolv.so.2 => /lib64/libresolv.so.2
librt.so.1 => /lib64/librt.so.1
libm.so.6 => /lib64/libm.so.6
libodbcinst.so.2 => /lib64/libodbcinst.so.2
libltdl.so.7 => /lib64/libltdl.so.7
libstdc++.so.6 => /lib64/libstdc++.so.6
libgcc_s.so.1 => /lib64/libgcc_s.so.1
libc.so.6 => /lib64/libc.so.6
ld-linux-x86-64.so.2 => /lib64/ld-linux-x86-64.so.2
0
我尝试将 pyodbc 和 pyarrow 升级到最新版本,行为是相同的:
pyarrow 13.0.0
pyodbc 4.0.39
我不确定问题是否专门针对 pyarrow,但基于 this bug 在导入 protobuf 时报告类似的行为,我在我的库中搜索了任何引用“protobuf”的内容,并且 pyarrow 弹出了一个头文件,其中包含该头文件姓名。可能是巧合,因为这是在旧版本的 pyarrow 中,而最新版本甚至不再有该文件。
FWIW,容器还有其他不会遇到此问题的 ODBC 驱动程序。
我认为 pyarrow init 正在改变环境中的某些内容,但我还不够 Pythonista,不知道如何识别什么;有进一步调试的技巧吗?
最终证明,这耗尽了分配给动态加载库的 TLS(线程本地存储)的 2048 字节。与 libarrow.so
相关的
pyarrow
对于这块内存来说是一头猪,并且在通过
pyodbc
加载 MySQL 驱动程序之前加载它导致
libmyodbc8a.so
将使用量推高到该限制。通过将其添加到
libarrow.so
环境变量来静态预加载
LD_PRELOAD
解决了我的问题。 (我第一次尝试预加载 libmyodbc8a.so,但这导致了一些其他问题我没有费心去追踪 - 不妨关注
pyarrow
,因为无论如何它都是内存消耗者。(
libtool
对于诊断确实没有帮助。我最终在本地编译了一个带有宏
LT_DEBUG_LOADERS
设置的版本并运行它以获得打印到STDERR的根本原因错误):“无法在静态TLS块中分配内存”。)