PAYATHON 2026

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 和驱动版本。

备注:内容仅供参考。