疑難排解 mssql-python

在使用 mssql-python 驅動程式連接 SQL Server、Azure SQL Database、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-python 並且 pyodbc 可以共存。 如果你發現衝突,請建立一個乾淨的虛擬環境:

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 入口網站的 Azure SQL 防火牆規則中加入你的客戶端 IP。
    • 若要連線到 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 Database、Azure SQL 受控執行個體 以及 Fabric 中的 SQL 資料庫,建議使用 Microsoft Entra 模式,例如 Authentication=ActiveDirectoryDefault
    • 如果你是刻意使用 SQL 認證,請確認伺服器是否允許,且你使用的登入格式是否正確。
  • 錯誤的 SQL 認證憑證

    • 請驗證使用者名稱和密碼。
    • 對於 Azure SQL,請包含完整使用者名稱: username@servername
  • 使用者不存在於資料庫中

    • 確認使用者是否能存取指定的資料庫。
    • 檢查登入是否對應到資料庫使用者。
  • 認證未設定

    • 建議使用 Microsoft Entra 認證:Authentication=ActiveDirectoryDefault
    • 如果你正在排除本機 SQL Server 應該接受 SQL 驗證的故障,請確認 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
)

Caution

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

交易未承諾

症狀:

關閉連線後,資料變更不會持續存在。

Solution:

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

Solution:

重試邏輯(參見 重試邏輯)負責立即失敗,但反覆出現的死結表示設計有問題。 為了解決根本原因,請擷取死結圖並分析涉及哪些語句與鎖類型。 常見的修正包括重新排序操作,使競爭交易能以相同順序取得鎖、縮小交易範圍,以及新增適當的索引以縮短鎖定時間。

欲了解完整的死結分析,請參閱 死結指南。 如果你正在使用 Azure SQL Database,請參閱「分析與防止死結」。

散裝載重問題

批量複製期間的限制違規

症狀:

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()

關於帶有暫存表的上繼模式,請參見 資料載入與移動模式

欄位映射錯誤

症狀:

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 的值不會乾淨俐落地對應到目標欄位類型。 常見情況包括:將值載入 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 整數或浮點數型別時,會默默地失效或引發資料型別錯誤。

原因:

像是 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)

容器與持續整合問題

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 格式寫入,並在達到 512 MB 時自動輪替,保留五個備份檔。 像密碼和存取權杖這類敏感資料會在日誌輸出中自動淨化。

若要在駕駛員日誌旁邊新增您自己的日誌條目,請使用 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

Caution

記錄日誌會產生效能負擔。 只在故障排除時啟用,不要預設在生產環境啟用。

取得駕駛資訊

從活躍連線中取得驅動程式版本及伺服器資料:

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 鎖定爭用 重試,然後 分析死結圖