mssql-python 故障排除

在使用 mssql-python 驱动连接 SQL Server、Azure SQL 数据库、Azure SQL 托管实例 和 Microsoft Fabric 中的 SQL 数据库时,诊断并解决常见问题。

安装问题

PIP安装失败或从源代码构建

症状:

error: Microsoft Visual C++ 14.0 or greater is required
ERROR: Failed building wheel for mssql-python

可能的原因和解决方案:

  • 没有适合你平台的预装轮毂

    • 确认你使用的是支持的Python版本(3.10及以上版本)和平台。 兼容性矩阵请参见 支持生命周期 。 在使用 pip install --upgrade pip 安装前,先升级 pip。 对于可重复的团队环境,可以使用可 重复部署 中的锁定工作流,或在 容器和本地开发 中使用容器模式,以减少本地机器漂移。
  • 虚拟环境未激活

    • 先激活你的虚拟环境。 安装到 Python 系统中可能会导致权限错误或冲突。
    python -m venv .venv
    .venv\Scripts\activate
    pip install mssql-python
    

  • 缺失的Linux系统库
    • 该驱动程序在 Linux 上需要一小部分系统库。 关于安装包的具体依赖,请参见 平台特定依赖

冲突的驱动程序安装

症状:

在同一环境中同时安装 mssql-pythonpyodbc 后,出现导入错误或异常行为。

修复:

mssql-pythonpyodbc 可以共存。 如果你发现冲突,创建一个干净的虚拟环境:

python -m venv .venv --clear
.venv\Scripts\activate
pip install mssql-python

连接问题

无法连接到服务器

症状:

OperationalError: [08001] (0) Client unable to establish connection

可能的原因和解决方案:

  • 服务器无法访问

    • 请确认服务器名称和端口是否正确。
    • 检查网络连接: ping servernametelnet servername 1433
    • 确保防火墙允许端口1433的出站连接。
  • SQL Server 无法运行

    • 请确认 SQL Server 服务已启动。
    • 对于命名实例,请确认 SQL Server 浏览器服务是否在运行。
  • Azure SQL firewall rules

    • 在 Azure 门户中将你的客户端 IP 添加到 Azure SQL 防火墙规则中。
    • 对于 Azure SQL 托管实例,请确保是从允许的网络进行连接。
# Test basic connectivity
import socket
try:
    sock = socket.create_connection(("<server>.database.windows.net", 1433), timeout=5)
    print("TCP connection successful")
    sock.close()
except Exception as e:
    print(f"Cannot reach server: {e}")

登录失败

症状:

OperationalError: [28000] (18456) Login failed for user 'username'.

可能的原因和解决方案:

  • 认证模式不匹配

    • 对于 Azure SQL 数据库、Azure SQL 托管实例 和 Fabric 中的 SQL 数据库,优先使用 Microsoft Entra 模式,例如 Authentication=ActiveDirectoryDefault
    • 如果你有意使用 SQL 认证,请确认服务器是否允许,并且你使用的登录格式是正确的。
  • 错误的SQL认证凭证

    • 核实用户名和密码。
    • 对于 Azure SQL,请包含完整用户名: username@servername
  • 数据库中不存在用户

    • 确认用户是否访问了指定的数据库。
    • 检查登录是否映射到数据库用户。
  • 认证未配置

    • 使用 Microsoft Entra 认证(推荐):Authentication=ActiveDirectoryDefault
    • 如果你正在排查本应接受 SQL 身份验证的本地 SQL Server,请确认 SQL Server 是否使用混合模式身份验证。

连接超时

症状:

OperationalError: [HYT00] (0) Timeout expired
OperationalError: [HYT01] (0) Connection timeout expired

可能的原因和解决方案:

  • 服务器响应缓慢

    • 增加连接超时时间:
    conn = mssql_python.connect(connection_string, timeout=60)
    
  • 网络延迟

    • 检查服务器的网络路径。
    • 考虑使用更短的网络路径或VPN。
  • 服务器负载过重

    • 试着在非高峰时段连接。
    • 请联系你的数据库管理员。

SSL 证书错误

症状:

OperationalError: [08001] SSL Provider: The certificate chain was issued by an authority that is not trusted

解决方案:

首先,优先选择可信证书或容器 和本地开发中的本地开发模式。 仅应将 TrustServerCertificate=yes 用于针对你所控制的服务器进行本地开发。

使用自签名证书进行开发和测试:

conn = mssql_python.connect(
    "Server=<server>.database.windows.net;"
    "Database=<database>;"
    "Authentication=ActiveDirectoryDefault;"
    "Encrypt=yes;"
    "TrustServerCertificate=yes;"  # Don't use in production
)

注意

TrustServerCertificate=yes 是仅限本地的备选方案。 不要把它带进共享开发容器、CI流水线或生产部署。 更广泛的指导请参见 加密与证书

在生产过程中,确保安装了正确的证书并使用:

conn = mssql_python.connect(
    "Server=<server>.database.windows.net;"
    "Database=<database>;"
    "Authentication=ActiveDirectoryDefault;"
    "Encrypt=yes;"
    "HostnameInCertificate=<server>.domain.com;"
)

查询执行问题

未找到表格或对象

症状:

ProgrammingError: [42S02] (208) Invalid object name 'TableName'.

可能的原因和解决方案:

  • 错误的数据库上下文

    # Ensure you're connected to the correct database
    cursor.execute("SELECT DB_NAME()")
    print(cursor.fetchone()[0])
    
  • 模式未指定

    # Use fully qualified name
    cursor.execute("SELECT * FROM dbo.TableName")
    
  • 表格不存在

    # Check if table exists
    cursor.execute("""
         SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES 
         WHERE TABLE_NAME = 'TableName'
    """)
    

语法错误

症状:

ProgrammingError: [42000] (102) Incorrect syntax near '...'.

解决方案:

  1. 先在SSMS中测试SQL 以验证语法

  2. 检查字符串转义:使用参数化查询:

    # Wrong - vulnerable to syntax issues and SQL injection
    cursor.execute(f"SELECT * FROM Production.Product WHERE Name = '{name}'")
    
    # Correct - use parameters
    cursor.execute("SELECT * FROM Production.Product WHERE Name = %(name)s", {"name": name})
    

参数误差

症状:

ProgrammingError: [07001] Wrong number of parameters

解决方案:

  1. 占位符和参数的数量——二者必须一致

  2. 选择合适的参数样式

    # Qmark style - positional
    cursor.execute("SELECT * FROM Production.Product WHERE ProductID = ? AND Name LIKE ?", (1, "Adjustable%"))
    print(cursor.fetchone())
    
    # Pyformat style - named
    cursor.execute("SELECT * FROM Production.Product WHERE ProductID = %(id)s AND Name LIKE %(name)s", {"id": 1, "name": "Adjustable%"})
    print(cursor.fetchone())
    

数据类型问题

日期时间转换错误

症状:

DataError: [22007] Invalid datetime format

解决方案:

使用Python的datetime对象代替字符串:

from datetime import datetime

cursor.execute("CREATE TABLE #Events (EventDate DATETIME)")

# Wrong - this raises an error for invalid dates
try:
    cursor.execute("INSERT INTO #Events (EventDate) VALUES (%(event_date)s)", {"event_date": "2024-13-45"})
except Exception as e:
    print(f"Expected error: {e}")

# Correct - use Python datetime objects
cursor.execute("INSERT INTO #Events (EventDate) VALUES (%(event_date)s)", {"event_date": datetime(2024, 3, 15)})
cursor.execute("SELECT EventDate FROM #Events")
print(cursor.fetchone())

十进制精度问题

症状:

数字似乎被截断或四舍五入。

解决方案:

使用 decimal.Decimal 表示精确的数值:

from decimal import Decimal

cursor.execute("CREATE TABLE #PriceDemo (ListPrice DECIMAL(10,2))")
# Preserve full precision
cursor.execute(
    "INSERT INTO #PriceDemo (ListPrice) VALUES (%(list_price)s)",
    {"list_price": Decimal("19.99")}
)

Unicode 编码问题

症状:

特殊字符会显得杂乱或导致错误。

解决方案:

  1. 在数据库中使用 NVARCHAR 列 来存储 Unicode 数据

  2. 直接传递字符串 ——驱动程序负责编码:

    cursor.execute("CREATE TABLE #UnicodeDemo (Name NVARCHAR(50))")
    cursor.execute("INSERT INTO #UnicodeDemo (Name) VALUES (%(name)s)", {"name": "日本語"})
    cursor.execute("SELECT Name FROM #UnicodeDemo")
    print(cursor.fetchone())
    

性能问题

查询执行缓慢

可能的原因和解决方案:

  • 缺少索引:检查SSMS中的查询执行计划。

  • 大型结果集:使用 fetchmany() 代替 fetchall()

    cursor.arraysize = 1000
    while True:
         rows = cursor.fetchmany()
         if not rows:
             break
         process_rows(rows)
    
  • 禁用连接池:启用串池:

    import mssql_python
    mssql_python.pooling(max_size=20, idle_timeout=300)
    

大型结果集的内存问题

症状:

Python进程内存耗尽。

解决方案:

  1. 以流式方式返回结果,而不是将所有结果都加载到内存中:

    cursor.execute("SELECT * FROM LargeTable")
    for row in cursor:  # Iterates one row at a time
        process_row(row)
    
  2. 使用服务器端分页

    page_size = 1000
    offset = 0
    while True:
        cursor.execute(
            "SELECT * FROM LargeTable ORDER BY ID "
            "OFFSET ? ROWS FETCH NEXT ? ROWS ONLY",
            (offset, page_size)
        )
        rows = cursor.fetchall()
        if not rows:
            break
        process_rows(rows)
        offset += page_size
    

交易问题

自动提交模式下的临时表作用域

在事务中创建的临时表(#tablename)在事务回滚时会消失。 当自动提交关闭(默认设置)时,这常常引起混淆:

conn = mssql_python.connect(connection_string)  # autocommit=False by default
cursor = conn.cursor()

cursor.execute("CREATE TABLE #TempData (ID INT, Name NVARCHAR(50))")
cursor.execute("INSERT INTO #TempData VALUES (1, 'test')")

# If the connection rolls back (explicit or on error), #TempData disappears
conn.rollback()

# This fails: Invalid object name '#TempData'
cursor.execute("SELECT * FROM #TempData")

修复方法: 创建临时表后立即提交,或使用自动提交模式:

cursor.execute("CREATE TABLE #TempData (ID INT, Name NVARCHAR(50))")
conn.commit()  # Lock in the table definition

cursor.execute("INSERT INTO #TempData VALUES (1, 'test')")
conn.commit()

需要自动提交模式的DDL语句,如 CREATE DATABASE,在未完成的事务中会失败。 在运行它们之前设置自动提交:

conn.autocommit = True
cursor.execute("CREATE DATABASE TestDB")
conn.autocommit = False

交易未提交

症状:

关闭连接后,数据更改不会持续存在。

解决方案:

使用 autocommit=False(默认值)时,你必须调用 commit()

cursor.execute("CREATE TABLE #Products (Name NVARCHAR(100))")
cursor.execute("INSERT INTO #Products (Name) VALUES (%(name)s)", {"name": "Widget"})
conn.commit()  # Don't forget this!

或者使用自动提交模式:

conn = mssql_python.connect(connection_string, autocommit=True)

死锁错误

症状:

OperationalError: [40001] (1205) Transaction ... was deadlocked on lock resources with another process

解决方案:

重试逻辑(参见 重试逻辑)处理即时失败,但反复出现的死锁表明设计存在问题。 为了解决根本原因,捕捉死锁图并分析涉及的语句和锁类型。 常见的修复方法包括重新排序操作,使竞争事务以相同顺序获得锁,缩小事务范围,以及添加适当的索引以缩短锁的持续时间。

关于死锁分析的完整攻略,请参见 死锁指南。 如果你正在使用 Azure SQL 数据库,请参见“分析并防止死锁”。

批量加载问题

批量复制期间的约束违规

症状:

RuntimeError: CHECK constraint ... Conflict occurred in database ...
RuntimeError: Cannot insert duplicate key ... violation of PRIMARY KEY constraint

原因:

你的批处理中的数据违反了表约束(主键、唯一键、CHECK或外键)。

修复:

加载前验证数据。 对于大型数据集,请先加载到暂存表中,然后再合并到目标表中:

# Load into staging, then validate
cursor.execute("CREATE TABLE ##Staging (ID INT, Name NVARCHAR(100))")
cursor.bulkcopy("##Staging", rows)

# Check for duplicates before merging
cursor.execute("""
    SELECT s.ID FROM ##Staging s
    INNER JOIN dbo.Target t ON s.ID = t.ID
""")
dupes = cursor.fetchall()
if dupes:
    print(f"Skipping {len(dupes)} duplicate rows")

# Insert only non-duplicate rows
cursor.execute("""
    INSERT INTO dbo.Target (ID, Name)
    SELECT s.ID, s.Name FROM ##Staging s
    WHERE NOT EXISTS (SELECT 1 FROM dbo.Target t WHERE t.ID = s.ID)
""")
conn.commit()

关于带有预留表的upsert模式,请参见 数据加载和移动模式

列映射错误

症状:

RuntimeError: Bulk copy failure - column count mismatch

原因:

你数据中的列数和目标表的列数不匹配,或者列的顺序错误。

修复:

确保你的数据在顺序和计数上完全符合表模式:

# Check the target table schema
cursor.execute("""
    SELECT COLUMN_NAME, DATA_TYPE
    FROM INFORMATION_SCHEMA.COLUMNS
    WHERE TABLE_NAME = 'MyTable'
    ORDER BY ORDINAL_POSITION
""")
for col in cursor.fetchall():
    print(col)

# Match your data to the column order
rows = [
    (1, "Widget", Decimal("19.99")),  # Must match table column order
    (2, "Gadget", Decimal("29.99")),
]
cursor.bulkcopy("dbo.MyTable", rows)

批量复制时出现类型不匹配

症状:

数据加载时,值被截断、四舍五入或错误。

原因:

Python 的值并不能干净地映射到目标列类型。 常见情况:将 float 值加载到 decimal 列中(精度丢失),或将过长字符串加载到定长列中。

修复:

使用与你模式匹配的正确 Python 类型:

from decimal import Decimal

# Use Decimal for decimal/numeric columns, not float
rows = [
    (1, "Widget", Decimal("19.99")),  # Correct
    # (1, "Widget", 19.99),           # Avoid: float loses precision
]
cursor.bulkcopy("dbo.Products", rows)

NumPy 类型绑定失败

症状:

参数在使用numpy整数或float类型时会默默失败或引发数据类型错误。

原因:

numpy.int64numpy.int32 这样的 NumPy 类型在 NumPy 2.x 中无法通过 isinstance(x, int)。 驱动程序的类型推断无法识别它们,导致意外行为。

修复:

绑定前将 numpy 值转换为原生 Python 类型:

import numpy as np

# Convert individual values
cursor.execute("SELECT * FROM Production.Product WHERE ProductID = %(product_id)s", {"product_id": int(np.int64(42))})

# Convert DataFrame values
for _, row in df.iterrows():
    cursor.execute(
        "INSERT INTO #Orders (ProductID, Qty) VALUES (%(product_id)s, %(qty)s)",
        {"product_id": int(row["ProductID"]), "qty": int(row["Qty"])}
    )

对于较大的数据集,可以使用 Arrowpandas 集成路径,它们内部处理类型转换。

使用临时表的批量复制

症状:

cursor.bulkcopy("#TempTable", data) 引发了 RuntimeError: Invalid object name '#TempTable'

原因:

bulkcopy() 由于元数据查找的限制,无法解析会话临时表(#tablename)。 全局温度表(##tablename)和永久表都有效。

修复:

可以使用全局温度表或常规预留表:

# Global temp table (visible to all sessions, dropped when last session disconnects)
cursor.execute("CREATE TABLE ##Staging (ID INT, Name NVARCHAR(50))")
cursor.bulkcopy("##Staging", rows)

# Or use a permanent staging table
cursor.execute("CREATE TABLE dbo.Staging (ID INT, Name NVARCHAR(50))")
cursor.bulkcopy("dbo.Staging", rows)

对于偏好使用会话临时表的小数据集,请改用 executemany()

cursor.execute("CREATE TABLE #Staging (ID INT, Name NVARCHAR(50))")
cursor.executemany("INSERT INTO #Staging (ID, Name) VALUES (?, ?)", rows)

容器和CI问题

Linux 上缺失的系统库

症状:

ImportError: libltdl.so.7: cannot open shared object file: No such file or directory
ImportError: libkrb5.so.3: cannot open shared object file

修复:

安装所需的系统包。 各套餐因发行版而异:

Distribution 安装命令
Ubuntu/Debian sudo apt-get install libltdl7 libkrb5-3 libgssapi-krb5-2
红帽 / Fedora sudo dnf install libtool-ltdl krb5-libs
Alpine apk add libltdl krb5-libs

关于 Dockerfile 示例,请参见 容器与本地开发

macOS 安装后出现的 SSL 错误

症状:

从macOS连接时,尤其是在Apple Silicon上,会出现与SSL相关的错误。

修复:

通过Homebrew安装OpenSSL,并设置链接标志:

brew install openssl
export LDFLAGS="-L/opt/homebrew/opt/openssl/lib"
export CPPFLAGS="-I/opt/homebrew/opt/openssl/include"

诊断工具

启用驱动程序日志

使用 mssql_python.setup_logging() 启用全面的 DEBUG 日志记录,以便进行故障排查。 所有驱动程序操作都会被记录,包括SQL语句、参数、内部ODBC操作和连接状态变化。

import mssql_python

# Enable logging to file (default)
mssql_python.setup_logging()

# Output to stdout (useful for CI/CD and containers)
mssql_python.setup_logging(output='stdout')

# Output to both file and stdout
mssql_python.setup_logging(output='both')

# Custom log file path (must use .txt, .log, or .csv extension)
mssql_python.setup_logging(log_file_path="/var/log/myapp/mssql.log")

日志文件以CSV格式编写,自动旋转,大小为512MB,并有五个备份。 像密码和访问令牌这样的敏感数据会在日志输出中自动净化。

要在驾驶日志旁边添加您自己的日志条目,请使用 driver_logger

from mssql_python.logging import driver_logger

mssql_python.setup_logging()

driver_logger.debug("[App] Starting data processing")
driver_logger.error("[App] Failed to process record")
# Your entries appear in the same file with the same format

注意

日志记录会带来性能开销。 只在排查故障时启用,默认不要在生产环境中启用。

获取驾驶员信息

从活跃连接中获取驱动版本和服务器详细信息:

import mssql_python

conn = mssql_python.connect(connection_string)

# Driver version
print(f"Version: {mssql_python.__version__}")

# Server information
print(f"Server name: {conn.getinfo(mssql_python.SQL_SERVER_NAME)}")
print(f"Database name: {conn.getinfo(mssql_python.SQL_DATABASE_NAME)}")

检查连接状态

在尝试操作前,先测试连接是否仍然开放:

try:
    cursor = conn.cursor()
    cursor.execute("SELECT 1")
    print("Connection is open")
except mssql_python.Error:
    print("Connection is closed or broken")

快速参考:常见错误

错误 SQLSTATE 常见原因 快速修复
客户端无法建立连接 08001 服务器无法访问 检查服务器名称/端口
登录失败 28000 凭据错误 验证用户名/密码
已超时 HYT00/HYT01 网络速度缓慢 延长超时时间
对象名称无效 42S02 错误的表/模式 使用完全限定的名称
语法错误 42000 SQL 错误 使用参数化查询
违反约束 23000 FK/PK违规 检查数据完整性
死锁 40001 锁争用 重试,然后 分析死锁图