mssql-django 故障排查

诊断并解决Microsoft Fabric中SQL Server、Azure SQL 数据库、Azure SQL 托管实例和 SQL 数据库的后端常见问题mssql-django。

mssql-django 2.0 支持默认的 PYODBC 驱动路径和可选的 MSSQL-Python 驱动路径。 欲了解更多信息,请参见 为 mssql-django 选择数据库驱动程序。

连接问题

本部分介绍最常见的连接错误以及如何解决这些问题。

在 pyodbc 路径上找不到 ODBC 驱动

症状:

django.core.exceptions.ImproperlyConfigured: 'ODBC Driver 18 for SQL Server' is not a recognized ODBC driver

或者:

Error: ('01000', "[01000] [unixODBC][Driver Manager]Can't open lib 'ODBC Driver 18 for SQL Server'")

可能的原因和解决方案:

  • 未安装 ODBC 驱动程序

    使用默认 pyodbc 路径时,安装 Microsoft ODBC 驱动 for SQL Server。 有关下载链接,请参阅下载 ODBC Driver for SQL Server。 mssql-python路径不使用外部安装的ODBC驱动程序。

  • 已安装多个驱动程序版本

    在 : 中 settings.py指定确切的驱动程序名称或路径:

    DATABASES = {
        "default": {
            "ENGINE": "mssql",
            "NAME": "<database>",
            "USER": "<user_id>",
            "PASSWORD": "<password>",
            "HOST": "<server>",
            "PORT": "1433",
            "OPTIONS": {
                "driver": "ODBC Driver 17 for SQL Server",
            },
        },
    }
    

    在 Linux 上,指定完整路径:

    "OPTIONS": {
        "driver": "/opt/microsoft/msodbcsql17/lib64/libmsodbcsql-17.10.so.6.1",
    },
    
  • 检查已安装的驱动程序

    • 在 Linux/macOS 上,运行 odbcinst -q -d。
    • 在Windows,在管理工具中检查 ODBC 数据源。

MSSQL-Python 拒绝连接选项

症状:

设置 "python_driver": "mssql_python" 的别名在连接设置期间失败,出现以下错误之一(在你将 pyodbc 连接字符串关键字移到 OPTIONS["extra_params"] 中之后):

mssql_python.exceptions.ConnectionStringParseError: Connection string parsing failed:
  Unknown keyword 'longasmax' is not recognized
mssql_python.exceptions.ConnectionStringParseError: Connection string parsing failed:
  Reserved keyword 'driver' is controlled by the driver and cannot be specified by the user

消息中的关键词名称为小写,所以你写的 关键字 LongAsMax 会显示为 longasmax。 ConnectionStringParseError 不属于 DB-API 例外层级,所以Django不会将其重新包装为 django.db.utils 错误。

可能的原因和解决方案:

  • extra_params 中仅限 pyodbc 的关键字

    mssql-python 路径会根据允许列表验证 extra_params。 DRIVER和APP保留供驾驶员使用,并生成Reserved keyword表单。 DSN、SERVERNAME、LongAsMax 以及仅限 pyodbc 的关键字(例如 ColumnEncryption、WSID、Network Library、UseFMTONLY、Current Language、Description、QuotedId、AnsiNPW、Regional、Connect Timeout 和 Unknown keyword)不在允许列表中,并生成 MARS_Connection 形式。 去掉关键词,或者用默认的pyodbc路径来设置需要该ODBC选项的别名。

  • 驱动程序选项预期用于控制mssql-python

    mssql-python 路径中会忽略 driver、dsn、host_is_server 和 unicode_results。 HOST 变 PORT 为 SERVER=<server>,<port>, HOST 空变为 localhost。

MSSQL-Python 依赖关系太老了

症状:

设置了 "python_driver": "mssql_python" 的别名在建立连接时会出现以下错误之一:

django.core.exceptions.ImproperlyConfigured: mssql-python 1.15.0 or newer is required; you have 1.14.0
django.core.exceptions.ImproperlyConfigured: The 'python_driver' connection option requests mssql-python, but the module could not be imported: No module named 'mssql_python'. Install it with 'pip install "mssql-python>=1.15.0"'.

第二种表单意味着 mssql_python 模块根本无法导入。

解决方案:安装 mssql-python>=1.15.0。 mssql-django 2.0 声明了 mssql-python>=1.15.0,因此普通的 pip install mssql-django 将在受支持的平台上解析为兼容版本。

驱动17的备用不适用于mssql-python

症状:

即使已安装 Microsoft ODBC Driver 17 for SQL Server,将别名设置为 "python_driver": "mssql_python" 仍然会失败。

本案没有明显的错误。 mssql-python 这一处理路径会静默忽略 driver 选项,因此连接会因相应的底层错误而失败。 如果你改为将驱动程序名称移到extra_params中,就会出现Reserved keyword 'driver'错误。 参见 mssql-python 拒绝连接选项。

解决方案:如果别名必须使用外部安装的 ODBC Driver 17,请使用默认的 pyodbc 路径。 mssql-python 路径不会回退到 Driver 17,并且会忽略 driver 选项。 这条路径不需要单独安装ODBC驱动。

连接被拒绝

症状:

django.db.utils.OperationalError: ('08001', '[08001] ... TCP Provider: Error code 0x2749 ...')

可能的原因和解决方案:

  • SQL Server上未启用 TCP/IP

    • 打开 SQL Server 配置管理器。
    • 在“SQL Server网络配置”下,启用 TCP/IP。
    • 在 TCP/IP 属性中,激活用于连接的 IP 地址。
    • 重启 SQL Server 服务。
  • 防火墙阻止端口 1433

    • 验证防火墙规则是否允许端口 1433 上的入站连接。
    • 对于Azure SQL,请在Azure门户防火墙设置中添加客户端 IP。
  • 服务器名称或端口错误

    验证配置中的 HOST 和 PORT 值。

登录失败

症状:

django.db.utils.OperationalError: ('28000', "[28000] [Microsoft][ODBC Driver 18 for SQL Server][SQL Server]Login failed for user '<user_id>'. (18456) (SQLDriverConnect); [28000] [Microsoft][ODBC Driver 18 for SQL Server][SQL Server]Login failed for user '<user_id>'. (18456)")

在 mssql-python 路径上:

django.db.utils.OperationalError: Driver Error: Invalid authorization specification; DDBC Error: [Microsoft][SQL Server]Login failed for user '<user_id>'.

可能的原因和解决方案:

  • 凭据不正确

    验证用户名和密码。

  • 数据库 NAME 不存在

    在 SQL Server 上,mssql-python 路径会像密码错误一样引发相同的 OperationalError,并显示相同的消息,因此仅凭这条消息无法判断你遇到的是哪一种情况。 在更改凭据之前,先确认数据库是否存在。 将NAME指向master,以单独测试登录:如果这样能够连接成功,说明凭证正确,问题出在数据库。 pyodbc 代码路径将这种情况单独标记为 Cannot open database "<database>" requested by the login. The login failed. (4060)。

    Azure SQL 数据库 对此情况的报告方式不同。 mssql-python 路径会引发 Driver Error: General error; DDBC Error: [Microsoft][SQL Server]Cannot open server "<server>" requested by the login. The login failed.。消息中指明了服务器,但服务器名称没有问题。 不如检查 NAME 一下。

  • 用户不存在

    确认登录名已映射到目标数据库中的用户。

  • 已禁用SQL Server身份验证

    启用混合模式身份验证,或使用Windows或Microsoft Entra身份验证。

连接超时

症状:

django.db.utils.OperationalError: ('HYT00', '[HYT00] [Microsoft][ODBC Driver 18 for SQL Server]Login timeout expired')

可能的原因和解决方案:

  • 网络延迟

    在 OPTIONS 中增大 connection_timeout。

  • 启用了自动暂停的 Azure SQL 数据库无服务器版

    自动暂停的数据库在首次连接尝试时恢复,该尝试可能因错误40613而失败,而数据库则继续。 将 connection_timeout 设置为至少 60,然后重试首次连接。 更多信息请参见 Azure SQL 数据库 serverless 以及自动暂停和自动恢复。

  • 服务器重载

    增加 connection_retries 和 connection_retry_backoff_time。

    "OPTIONS": {
        "driver": "ODBC Driver 18 for SQL Server",
        "connection_timeout": 30,
        "connection_retries": 5,
        "connection_retry_backoff_time": 10,
    },
    

迁移问题

在针对 SQL Server 的 Django 迁移操作期间发生这些错误。

原生 SQL 和 GROUP BY 问题

这些错误发生在带有 GROUP BY 子句的原始或注释查询通过后端的占位符重写步骤时。

在带有转义 %% 和实际参数的 GROUP BY 上出现 IndexError

症状:

IndexError: Replacement index N out of range for positional args tuple

该查询在没有 GROUP BY 子句时有效,在没有转义的 %% 字面量时也有效,但当两者与实际的 %s 参数同时存在时则失败。

解决方案:升级到最新版本 mssql-django 。 后端将占位符重写正则表达式缩小为 %% 仅 和 %s ,因此逃逸 %% 的文字被逐字保留,且不会注入幻影占位符。

在原始 GROUP BY 查询中,针对 IntegerChoices 出现 NotImplementedError

症状:

NotImplementedError: Not supported type <enum '...'> (StatusChoices.IN_PROGRESS)

相同的枚举值在ORM查询和无 GROUP BY的原始查询中都有效,但当作为参数传递给包含 GROUP BY 子句的原始查询时,则失败。

解决方案:升级到最新版本 mssql-django 。 后端在 GROUP BY 路径中使用 isinstance 进行参数类型检查,因此 IntegerChoices(int 的子类)能够正确绑定。 bool 仍会绑定 bit,且普通 int 保持不变。

正则表达式查找问题

__regex 或 __iregex 未返回任何行

症状:查询运行时无错误,结果集为空,尽管行与模式相符。

Product.objects.filter(name__regex=r"^Widget \d+$")  # no rows, though "Widget 42" exists

原因: dbo.REGEXP_LIKE 忽略了图案中的字面空白。 该模式会被视为 ^Widget\d+$ 来进行匹配,因此任何包含空格的值都无法满足该模式。 没有任何错误被引发,因此空结果看起来像是数据问题。

解决方案:将空白字符写成转义序列或字符类:

Product.objects.filter(name__regex=r"^Widget\s\d+$")
Product.objects.filter(name__regex=r"^Widget[ ]\d+$")

Cannot find ... dbo.REGEXP_LIKE

症状:

django.db.utils.ProgrammingError: ('42000', '[42000] [Microsoft][ODBC Driver 18 for SQL Server][SQL Server]Cannot find either column "dbo" or the user-defined function or aggregate "dbo.REGEXP_LIKE", or the name is ambiguous. (4121) (SQLExecDirectW)')

在 mssql-python 路径上:

django.db.utils.ProgrammingError: Driver Error: Syntax error or access violation; DDBC Error: [Microsoft][SQL Server]Cannot find either column "dbo" or the user-defined function or aggregate "dbo.REGEXP_LIKE", or the name is ambiguous.

原因:你查询的数据库中没有安装CLR汇编。 它是按数据库安装的,不是按服务器安装的。

解决方案:对该数据库运行 python manage.py install_regex_clr <database>。 删除并重新创建数据库后,再运行一次。 参见 设置正则表达式查询。

日期和时间问题

Now() 值在 USE_TZ=True 时会发生偏移

症状:

当 SQL Server 主机时区不是 UTC 时,使用 Django Now()、auto_now 或 auto_now_add 写入的时间戳会发生偏移。

解决方案:升级到最新版本 mssql-django 。 后端生成支持时区的 Now() SQL,保留 datetimeoffset 偏移量,并通过 tzdata 和 zoneinfo 读取时区数据。

AttributeError 调用 .explain() 时

症状:

AttributeError: ... explain_format ...

解决方案:升级到最新版本 mssql-django 。 后端负责解释每个支持的 Django 版本的元数据。

无法修改 AutoField

症状:

django.db.utils.ProgrammingError: Cannot alter column to or from an IDENTITY column

解决方案:SQL Server 不支持将字段从 AutoField 来回更改。 使用所需的字段类型创建新模型,手动迁移数据,然后删除旧表。 有关解决方法,请参阅 使用 mssql-django 进行数据库迁移。

重命名因外键约束而失败

症状:

django.db.utils.ProgrammingError: ... could not drop constraint ...

解决方案:SQL Server在重命名列之前需要删除外键约束。 在迁移过程中使用 SeparateDatabaseAndState。 有关示例,请参阅 使用 mssql-django 进行数据库迁移。

编码问题

编码错误通常发生在 pyodbc 路径上,当 pyodbc SQL Server 的字符数据被误解时。

Unicode 编码错误

症状:

UnicodeDecodeError: 'utf-8' codec can't decode byte ...

解决方案:在 OPTIONS 词典中配置 pyodbc 编码。 mssql-python路径忽略 unicode_results了。

"OPTIONS": {
    "driver": "ODBC Driver 18 for SQL Server",
    "unicode_results": True,
},

FreeTDS 问题

FreeTDS 需要 pyodbc 专用配置,这与 Microsoft ODBC 驱动不同。

host_is_server错误

症状:

使用 FreeTDS 而不指定 host_is_server时连接失败。

解决方案:使用 FreeTDS 时,将 host_is_server 设置为 True:

"OPTIONS": {
    "driver": "FreeTDS",
    "host_is_server": True,
},

有关 FreeTDS 配置的详细信息,请参阅 mssql-django 的连接选项。

测试数据库问题

测试数据库创建和销毁可能会失败,具体取决于身份验证方法。

无法使用托管标识创建测试数据库

症状:

django.db.utils.DatabaseError: ('42000', '[42000] ... EXECUTE permission denied on object ...')

或者:

django.db.utils.OperationalError: ('28000', ... login failed ...)

使用 ActiveDirectoryMsi (托管标识)身份验证时,测试运行程序无法创建或销毁测试数据库。 存在此限制是因为:

  • 托管标识凭据是从主机环境(例如Azure VM 和应用服务)获取的。

  • 测试运行器在拆卸期间尝试使用测试数据库凭据连接。

  • 可以授予托管标识数据库级角色,但测试数据库的创建和删除通常需要测试运行器通常没有的服务器级权限。

受影响的身份验证方法:

  • ActiveDirectoryMsi(Azure托管标识)
  • ActiveDirectoryServicePrincipal(仅在服务器范围配置时)

支持的身份验证方法 (测试数据库创建工作):

  • ActiveDirectoryPassword
  • ActiveDirectoryIntegrated
  • SQL 身份验证(用户名/密码)

测试环境中的身份验证取舍

方法 无机密 支持自动创建/删除测试数据库 典型用法
ActiveDirectoryMsi Yes 通常不(除非授予服务器级权限) Azure托管的生产工作负荷
ActiveDirectoryServicePrincipal 否(客户端密码/证书) 取决于授予的服务器级权限 采用显式身份管理的 CI/CD
ActiveDirectoryPassword No 是(具有足够的 SQL 权限) 开发人员和受控的 CI 环境
SQL 身份验证 No 是(具有足够的 SQL 权限) 本地或隔离的测试环境

解决方法:

  • 在开发环境中:使用 --keepdb 标志跳过测试数据库清理:

    python manage.py test --keepdb
    
  • 对于 CI/CD 管道:预先创建专用测试数据库并授予托管标识 CREATE TABLE 和 ALTER 权限:

    -- Connect as a server admin, then:
    USE [test_database_name];
    
    -- Grant permissions for managed identity (replace with your identity name)
    CREATE USER [your-app-identity] FROM EXTERNAL PROVIDER;
    GRANT CREATE TABLE TO [your-app-identity];
    GRANT ALTER ON SCHEMA::dbo TO [your-app-identity];
    
  • 替代方法:对测试环境使用 SQL 身份验证,或切换到 ActiveDirectoryPassword CI/CD 测试运行程序。

回滚流程

当迁移在中途失败时,请使用此回滚顺序将系统恢复到已知正常状态:

  1. 停止应用程序写入以避免其他架构偏移。

  2. 检查迁移状态:

    python manage.py showmigrations
    python manage.py sqlmigrate <app_label> <migration_number>
    
  3. 回滚到最后一个已知良好的迁移:

    python manage.py migrate <app_label> <previous_migration>
    
  4. 如果模式与迁移历史记录不一致,请仅在核实数据库的实际模式后,谨慎使用 --fake 修复状态。

  5. 首先在过渡环境中重新运行迁移,然后重试生产。

Important

对于破坏性的迁移(如删除、重命名和列类型更改),请在部署之前执行经过测试的备份。 如果无法通过迁移回滚,则从备份还原并重新应用已验证的迁移。

Docker 和容器问题

容器镜像在使用默认 pyodbc 路径时,需要明确安装 ODBC 驱动和构建依赖。 mssql-python 方案不需要单独安装 ODBC 驱动程序,但仍然需要 unixODBC 运行时,因为 Django 在加载该后端时会导入 pyodbc。

容器中找不到 ODBC 驱动程序

症状:

Error: ('01000', "[01000] [unixODBC][Driver Manager]Can't open lib 'ODBC Driver 18 for SQL Server'")

可能的原因和解决方案:

  • 容器映像中未安装 ODBC 驱动程序

    Slim 或 Alpine 基础镜像不包含 ODBC 驱动程序。 使用 pyodbc 时,请在 Dockerfile 中添加 Microsoft APT 存储库并安装 msodbcsql18。 有关完整的 Dockerfile 示例,请参阅 “部署到应用服务 ”。

  • 缺少 unixodbc-dev 包

    pyodbc wheel 链接到 libodbc.so。 在安装Python包之前安装 unixodbc-dev (Debian/Ubuntu) 或 unixODBC-devel (RHEL/Fedora)。

  • apt-get autoremove 在安装驱动程序后删除了 libgssapi-krb5-2

    msodbcsql18 在运行时加载 libgssapi-krb5-2,但未将其声明为依赖项。 该库通常作为 curl 的依赖项出现,因此使用 --auto-remove 清除 curl,或随后运行 apt-get autoremove,都会将其移除。 镜像构建正常,但随后所有连接都失败了。 明确安装 libgssapi-krb5-2 ,安装驱动后不要自动移除。

安装版本 18 时,系统显示驱动程序 17 缺失

症状:

Error: ('01000', "[01000] [unixODBC][Driver Manager]Can't open lib 'ODBC Driver 17 for SQL Server' : file not found (0) (SQLDriverConnect)")

错误中提到的是版本 17,但 odbcinst -q -d 显示已注册的是版本 18,而 dpkg -l msodbcsql18 显示该版本已安装。

原因:18 版本已注册但加载失败,因此 mssql-django 退回到未安装的 17 版本。 回退机制报告的是它第二个尝试的驱动程序,而不是失败的那个。

解决方案:安装 libgssapi-krb5-2 并重建。 请参见前面的自动移除说明,了解该库是如何丢失的。

在容器中加载 pyodbc 模块时出错

症状:

django.core.exceptions.ImproperlyConfigured: Error loading pyodbc module: libodbc.so.2: cannot open shared object file: No such file or directory

原因:该镜像没有 unixODBC 运行时。 mssql-django 会在 Django 加载该后端时导入 pyodbc,因此该错误在 mssql-python 路径下也会出现,甚至在尝试建立任何连接之前就会出现。

解决方案:安装 unixodbc (或 unixodbc-dev)。

MSSQL-python 驱动加载失败

症状:

django.db.utils.OperationalError: Driver Error: Connection operation failed; DDBC Error: Failed to load the driver.

原因:随 mssql-python 一起提供的驱动程序需要 Kerberos 运行时库,而精简基础镜像不包含这些库。

解决方案:安装 libkrb5-3 和 libgssapi-krb5-2。

pyodbc 在 slim 映像上构建失败

症状:

error: command 'gcc' failed: No such file or directory

或者:

fatal error: sql.h: No such file or directory

解决方案:先安装构建依赖项,再执行 pip install:

RUN apt-get update && apt-get install -y --no-install-recommends \
    gcc \
    g++ \
    unixodbc-dev

或者,使用多阶段生成来保持最终映像较小:

# Build stage
FROM python:3.12-slim AS builder
RUN apt-get update && apt-get install -y --no-install-recommends gcc g++ unixodbc-dev
COPY requirements.txt .
RUN pip wheel --no-cache-dir --wheel-dir /wheels -r requirements.txt

# Runtime stage
FROM python:3.12-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
    curl gnupg2 unixodbc \
    && curl -fsSL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor -o /usr/share/keyrings/microsoft-prod.gpg \
    && curl -fsSL https://packages.microsoft.com/config/debian/12/prod.list > /etc/apt/sources.list.d/mssql-release.list \
    && apt-get update \
    && ACCEPT_EULA=Y apt-get install -y --no-install-recommends msodbcsql18 libgssapi-krb5-2 \
    && apt-get purge -y curl gnupg2 \
    && rm -rf /var/lib/apt/lists/*
COPY --from=builder /wheels /wheels
RUN pip install --no-cache-dir /wheels/*

容器无法连接到SQL Server

症状:

django.db.utils.OperationalError: ('08001', '... TCP Provider: Error code 0x2749 ...')

可能的原因和解决方案:

  • Docker Compose 服务名称未被用作主机名

    使用 Docker Compose 时,设置为 DB_HOST 服务名称(例如, db而不是 localhost 或 127.0.0.1)。

  • SQL Server容器未就绪

    SQL Server容器需要几秒钟才能启动。 添加健康检查或启动延迟:

    services:
      db:
        image: mcr.microsoft.com/mssql/server:2022-latest
        healthcheck:
          test: /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P "$$MSSQL_SA_PASSWORD" -No -Q "SELECT 1" || exit 1
          # $$ escapes the $ sign in Docker Compose YAML
          interval: 10s
          retries: 10
          start_period: 10s
      web:
        depends_on:
          db:
            condition: service_healthy
    
  • 端口映射冲突

    如果主机上运行SQL Server的另一个实例,请更改公开的端口(例如1434:1433),并相应地更新 Django 配置。

Azure SQL暂时性错误恢复

后端mssql-django通过查询SERVERPROPERTY('EngineEdition')自动检测Azure SQL 数据库和Azure SQL 托管实例连接。 针对Azure SQL运行时,后端会针对暂时性错误(例如临时资源限制或短暂的网络中断)重试连接。

您可以使用 connection_retries 和 connection_retry_backoff_time 选项来调整这种行为:

"OPTIONS": {
    "driver": "ODBC Driver 18 for SQL Server",
    "connection_retries": 5,
    "connection_retry_backoff_time": 5,
},

这些设置仅适用于初始连接建立。 后端不会重试失败的查询。 如果在建立连接后查询失败并出现暂时性错误,则异常将传播到应用程序代码。 使用应用程序级重试逻辑(例如 django-retry-db 或自定义中间件)进行查询级复原。

慢查询和计划回归

这些问题通常需要服务器端分析以及 Django 级查询评审。

查询变慢或开始超时

症状:

随着时间的推移,相同的查询集变慢,或在部署、索引更改或统计信息更新后开始超时。

可能的原因和解决方案:

  • 从内置性能报告开始

    对于SQL Server和Azure SQL 托管实例,请在SQL Server Management Studio中打开性能仪表板。 对于 Azure SQL 数据库,请打开Azure SQL 数据库的查询性能见解。 这些工具通常是比即席 DMV 查询更好的第一步,因为它们可以快速显示昂贵的查询、等待和资源压力。

  • 计划回归

    使用查询存储查找慢速查询,并检查它是否具有多个计划。 从使用查询存储监视工作负荷的最佳做法中描述的回归查询和资源消耗最多的查询视图开始。

  • 低效的执行计划

    打开语句的实际执行计划,并检查表或索引扫描、大型键查找、哈希溢出或不准确的行估计。 有关背景信息,请参阅 执行计划概述。

  • 识别出错误的瓶颈

    如果查询没有 CPU 绑定,请使用查询存储等待统计信息并识别瓶颈来区分 CPU、内存、磁盘 I/O、阻塞和连接压力。

  • 修复被应用在错误的层

    采取最小且有效的修复措施:添加或调整索引、更新统计信息、减少所选的列数和行数,或将大型写入分批进行。 如果需要应急缓解措施,DBA 可以在修复根本原因的同时,暂时在 查询存储 中强制使用一个已知良好的计划。

将 dbshell 用于交互式查询

Django 的 dbshell 管理命令会打开一个连接到你的数据库的交互式 SQL shell:

python manage.py dbshell

配置 Microsoft ODBC 驱动程序时,后端使用 sqlcmd;使用 FreeTDS 时,后端使用 isql。 验证该工具是否在 PATH 上:

  • Windows:sqlcmd包含在SQL Server工具中,也可以单独下载。
  • Linux 和 macOS:从Microsoft存储库安装mssql-tools18。