pyodbc 连接 MSSQL 时出现 SQLDriverConnect 错误
结论
首先检查连接字符串中的 SERVER="192.168.1.106"。ODBC 连接字符串不会按照 Python 或 Shell 的规则处理双引号,FreeTDS 可能将双引号视为服务器地址的一部分,最终尝试连接的主机会变成 "192.168.1.106"。
去掉双引号后再试:
import pyodbc
connection = pyodbc.connect(
'DRIVER={FreeTDS};'
'SERVER=192.168.1.106;'
'PORT=1433;'
'DATABASE=myDB;'
'UID=myUsername;'
'PWD=myPassword;'
'TDS_Version=8.0;'
)
isql odbc1 ... 已经连接成功,因此也可以让 pyodbc 直接复用这个 DSN:
import pyodbc
connection = pyodbc.connect(
'DSN=odbc1;'
'DATABASE=myDB;'
'UID=myUsername;'
'PWD=myPassword;'
)
如果使用 DSN 仍然失败,应检查 pyodbc 是否链接到了 Homebrew 安装的 unixODBC。Mac 上可能同时存在 unixODBC、iODBC 和 Anaconda 自带的库,pyodbc 很容易因此链接到另一套 ODBC 环境。
为什么其他客户端能连接,pyodbc 却可能失败
这些工具使用的连接链路不同:
tsql直接使用 FreeTDS,不经过 ODBC Driver Manager。isql使用 unixODBC 和 FreeTDS。- Navicat 使用自己的驱动和配置。
- pyodbc 使用编译时链接的 ODBC Driver Manager。
tsql 和 Navicat 连接成功,说明服务器、端口和账号基本可用。isql 连接成功,则说明当前终端使用的 unixODBC 配置正常。不过,Anaconda 环境中的 pyodbc 未必使用同一套 unixODBC、驱动注册信息和配置文件。
排查步骤
1. 使用已经验证过的 DSN
先运行一个最小测试:
import pyodbc
cnxn = pyodbc.connect(
'DSN=odbc1;UID=myUsername;PWD=myPassword;',
timeout=5
)
cursor = cnxn.cursor()
cursor.execute('SELECT 1')
print(cursor.fetchone())
如果这段代码能够连接,问题就在原来的无 DSN 连接字符串中,可以排除网络和 SQL Server 本身的问题。
需要指定数据库时,先确认基本连接正常,再加入数据库名称:
cnxn = pyodbc.connect(
'DSN=odbc1;DATABASE=myDB;UID=myUsername;PWD=myPassword;',
timeout=5
)
这样可以判断失败发生在连接服务器阶段,还是打开指定数据库阶段。
2. 检查 pyodbc 能识别的驱动
在 Anaconda virtualenv 中运行:
import pyodbc
print(pyodbc.drivers())
Python 2.7 可以使用:
import pyodbc
print pyodbc.drivers()
输出中应包含 FreeTDS,并且名称要与连接字符串中的配置完全一致:
DRIVER={FreeTDS}
如果列表为空,或者其中没有 FreeTDS,说明 pyodbc 当前加载的 ODBC Driver Manager 没有读取到 Homebrew 的驱动注册信息。即使 isql 可以正常连接,pyodbc 也可能正在使用另一套 ODBC 环境。
3. 确认 unixODBC 读取的配置文件
运行:
odbcinst -j
该命令会显示 unixODBC 的安装位置,以及系统级 odbc.ini、odbcinst.ini 和用户级 DSN 配置的位置。
检查这些路径是否指向实际使用的配置目录。除了 ~/.odbc.ini,还需要确认 odbcinst.ini 中已经注册 FreeTDS 驱动,例如:
[FreeTDS]
Description = FreeTDS Driver
Driver = /usr/local/lib/libtdsodbc.so
Setup = /usr/local/lib/libtdsodbc.so
具体路径由 Homebrew 的安装位置决定,不能直接照搬示例。可以查看当前已注册的驱动:
odbcinst -q -d
再查看 FreeTDS 的具体配置:
odbcinst -q -d -n FreeTDS
4. 检查 pyodbc 链接到了 unixODBC 还是 iODBC
先找到 pyodbc 扩展文件:
python -c "import pyodbc; print(pyodbc.__file__)"
然后检查它依赖的动态库:
otool -L /path/to/pyodbc.so
将 /path/to/pyodbc.so 替换为上一步输出的路径。
输出中出现 libodbc,通常表示 pyodbc 链接到了 unixODBC;出现 libiodbc,则表示链接到了 iODBC。除了库名,还要检查实际路径是否属于预期的 Homebrew 安装。
如果 pyodbc 链接到了错误的 Driver Manager,需要在 Anaconda virtualenv 中使用 unixODBC 的头文件和库路径重新编译安装 pyodbc。重新安装前,先确认以下命令指向的环境:
which python
which isql
which odbcinst
它们应指向预期位置,避免编译和运行分别使用两套库。
5. 简化无 DSN 连接字符串
排查时先去掉非必要参数,只保留服务器、端口和身份验证信息:
import pyodbc
cnxn = pyodbc.connect(
'DRIVER={FreeTDS};'
'SERVER=192.168.1.106;'
'PORT=1433;'
'UID=myUsername;'
'PWD=myPassword;'
'TDS_Version=8.0;',
timeout=5
)
连接成功后,再加入数据库参数:
DATABASE=myDB;
最后添加字符集配置。逐项增加参数,比较容易确定是哪个属性导致连接失败。
FreeTDS 在不同版本和配置方式下可能使用 ClientCharset 指定字符集,例如:
cnxn = pyodbc.connect(
'DRIVER={FreeTDS};'
'SERVER=192.168.1.106;'
'PORT=1433;'
'DATABASE=myDB;'
'UID=myUsername;'
'PWD=myPassword;'
'TDS_Version=8.0;'
'ClientCharset=UTF-8;'
)
是否需要这个参数,取决于已安装的 FreeTDS 版本和全局配置。验证基本连接时不需要它,确认连接正常后再添加即可。
推荐配置
isql odbc1 myUsername myPass 已经验证成功,可以优先复用该 DSN:
import pyodbc
connection_string = (
'DSN=odbc1;'
'DATABASE=myDB;'
'UID=myUsername;'
'PWD=myPassword;'
)
try:
connection = pyodbc.connect(connection_string, timeout=5)
cursor = connection.cursor()
cursor.execute('SELECT 1')
print(cursor.fetchone())
finally:
if 'connection' in locals():
connection.close()
如果 DSN 可以连接,而无 DSN 方式失败,需要比较两种方式实际使用的驱动、服务器属性和 TDS 配置。原连接字符串中的双引号应先去掉,字符集等附加参数则等基本连接确认后再加入。
注意事项
- ODBC 属性使用分号分隔。如果密码包含分号等特殊字符,需要按照 ODBC 连接字符串的规则转义,不能直接拼接。
TDS_Version、tds version等属性是否受支持,以及适合使用哪个值,取决于安装的 FreeTDS 版本。~/.odbc.ini用于定义 DSN,odbcinst.ini用于注册 ODBC 驱动,两者用途不同。- 手动执行
python setup.py install无法保证 pyodbc 链接到正确的 unixODBC。编译时找到哪套头文件和库,pyodbc 就可能使用哪套环境。 - Python 2.7、OS X Yosemite 及其相关依赖都已较旧。条件允许时,可在完成兼容性验证后迁移到仍受支持的 Python、macOS 和驱动版本。
备注:内容仅供参考。
