Fehlerbehebung von Abfrage-, Daten- und Betriebsproblemen mit mssql-python

Nutzen Sie diesen Artikel, um Probleme mit der Abfrageausführung, Datentyp, Performance, Transaktionen und Massenkopien mit dem mssql-python Treiber zu diagnostizieren.

Abfrageausführungsprobleme

Tabelle oder Objekt nicht gefunden

Symptome:

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

Mögliche Ursachen und Lösungen:

  • Falscher Datenbankkontext

    cursor.execute("SELECT DB_NAME()")
    print(cursor.fetchone()[0])
    
  • Schema nicht spezifiziert

    cursor.execute("SELECT * FROM dbo.TableName")
    
  • Tabelle existiert nicht

    cursor.execute("""
        SELECT TABLE_NAME
        FROM INFORMATION_SCHEMA.TABLES
        WHERE TABLE_NAME = 'TableName'
    """)
    

Syntaxfehler

Symptome:

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

Lösungen :

  1. Testen Sie die SQL-Anweisung in SQL Server Management Studio (SSMS), um die Syntax zu überprüfen.

  2. Verwenden Sie eine parametrisierte Abfrage anstelle von String-Interpolation:

    # Don't use string interpolation for query parameters.
    cursor.execute(f"SELECT * FROM Production.Product WHERE Name = '{name}'")
    
    # Use parameters.
    cursor.execute(
        "SELECT * FROM Production.Product WHERE Name = %(name)s",
        {"name": name},
    )
    

Parameterfehler

Symptome:

ProgrammingError: [07001] Wrong number of parameters

Lösungen :

  1. Zähle die Platzhalter und Parameter. Die Zählungen müssen übereinstimmen.

  2. Wählen Sie den richtigen Parameterstil:

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

Falsche Zeilenanzahlen bei Batches mit mehreren Ergebnissen

Wenn rowcount nach nextset() die Anzahl der ersten Anweisung wiederholt, aktualisieren Sie den Treiber. Jedes erfolgreiche nextset() aktualisiert die Anzahl für das neue Ergebnis. Ein Reihen-erzeugendes Ergebnis beginnt bei -1 vor dem Abrufen. Überprüfen cursor.description Sie vor dem Abruf, da Datenänderungsergebnisse Zählungen statt Zeilen enthalten.

Lassen Sie SET NOCOUNT weg, wenn Sie Ergebnisse für betroffene Zeilen benötigen. Ein vollständiges Beispiel finden Sie unter Jedes Ergebnis in einem Batch verfolgen.

Probleme mit Datentypen

Fehler bei Date-Time-Umrechnungen

Symptome:

DataError: [22007] Invalid datetime format

Solution:

Verwende Python-Objekte datetime statt Strings.

from datetime import datetime

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

# This value raises an error because the date is invalid.
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}")

# Use a Python datetime object.
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())

Der Zeitstempel-Konstruktor erfordert ein Mikrosekundenargument

Wenn Timestamp(year, month, day, hour, minute, second)TypeError bei einem fehlenden microsecond-Argument auslöst, upgraden Sie mssql-python. Der aktuelle Treiber akzeptiert den sechs-Argumente-DB-API Konstruktor und setzt microsecond standardmäßig auf 0. Weitere Informationen finden Sie unter DB-API-Zeitstempelkonstruktor.

Dezimalpräzisionsprobleme

Symptome:

Zahlen erscheinen abgeschnitten oder falsch gerundet.

Solution:

Verwendung decimal.Decimal für präzise Zahlenwerte:

from decimal import Decimal

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

Unicode-Codierungsprobleme

Symptome:

Spezialzeichen erscheinen verwirrt oder verursachen Fehler.

Lösungen :

  1. Verwenden Sie für Unicode-Daten in Ihrer Datenbank Spalten vom Typ nvarchar.

  2. Übergeben Sie Zeichenfolgen direkt. Der Treiber übernimmt die Codierung:

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

Leistungsprobleme

Langsame Abfrageausführung

Mögliche Ursachen und Lösungen:

  • Fehlende Indizes: Überprüfen Sie den Abfrageausführungsplan in SSMS.

  • Große Ergebnismengen: Verwenden fetchmany() Sie statt :fetchall()

    cursor.arraysize = 1000
    while True:
        rows = cursor.fetchmany()
        if not rows:
            break
        process_rows(rows)
    
  • Verbindungspooling deaktiviert: Pooling aktivieren:

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

Speicherprobleme bei großen Ergebnismengen

Symptome:

Der Python-Prozess geht dem Speicher aus.

Lösungen :

  1. Streame Ergebnisse, anstatt alle Zeilen in den Speicher zu laden.

    cursor.execute("SELECT * FROM LargeTable")
    for row in cursor:
        process_row(row)
    
  2. Verwende serverseitige Paginierung.

    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
    

Transaktionsprobleme

Gültigkeitsbereich von temporären Tabellen mit Autocommit

Sitzungstemporäre Tabellen (#tablename), die du innerhalb einer Transaktion erstellst, verschwinden, wenn die Transaktion zurückrollt. Dieses Verhalten führt häufig zu Verwirrung, wenn Autocommit deaktiviert ist, was standardmäßig der Fall ist:

conn = mssql_python.connect(connection_string)
cursor = conn.cursor()

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

# An explicit rollback or an error removes #TempData.
conn.rollback()

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

Commit unmittelbar, nachdem du eine temporäre Tabelle erstellt hast, oder nutze den Autocommit-Modus:

cursor.execute("CREATE TABLE #TempData (ID INT, Name NVARCHAR(50))")
conn.commit()

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

DDL-Anweisungen, die den Autocommit-Modus erfordern, wie CREATE DATABASE, schlagen innerhalb einer offenen Transaktion fehl. Setze Autocommit ein, bevor du sie ausführst:

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

Transaktion nicht abgeschlossen

Symptome:

Datenänderungen bleiben nicht bestehen, nachdem du die Verbindung geschlossen hast.

Solution:

Mit autocommit=False, was die Standardeinstellung ist, rufen Sie commit() auf:

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

Alternativ verwenden Sie den Autocommit-Modus:

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

Deadlock-Fehler

Symptome:

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

Solution:

Die Wiederholungslogik übernimmt das unmittelbare Scheitern, aber wiederkehrende Deadlocks deuten auf ein Designproblem hin. Erfassen Sie den Deadlock-Graphen und analysieren Sie die Anweisungen und Sperrtypen. Häufige Korrekturen umfassen folgende Änderungen:

  • Ordnen Sie die Operationen so neu, dass konkurrierende Transaktionen Sperren in derselben Reihenfolge anfordern.
  • Reduzieren Sie den Transaktionsumfang.
  • Fügen Sie passende Indizes hinzu, um die Sperrdauer zu verkürzen.

Eine vollständige Anleitung zur Deadlock-Analyse finden Sie im Leitfaden zu Deadlocks. Wenn Sie eine Azure SQL-Datenbank verwenden, siehe Analysieren und Deadlocks verhindern.

Probleme beim Massenladen

Die Massenkopie schlägt nach einem Paket-Upgrade fehl

Wenn reguläre Abfragen funktionieren, aber bulkcopy() oder bulkcopy_arrow() das native Bulk-Copy-Modul nicht importieren oder laden, aktualisieren Sie den Treiber mit Abhängigkeiten:

python -m pip install --upgrade mssql-python
python -m pip check

Der Treiber benötigt eine entsprechende mssql-python-rs Runtime. Ein Teil-Upgrade oder eine Installation mit --no-deps kann inkompatible Pakete installiert zurücklassen. Wenn du einen privaten Paketindex verwendest, spiegele beide Begleitpakete und bewahre die deklarierten Abhängigkeitspins des Treibers. Für die aktuellen Pins, siehe Anforderungen an Abhängigkeiten.

Nicht unterstützte Zielnamen

bulkcopy() und bulkcopy_arrow() akzeptieren höchstens drei Teile des Bezeichners. Ein vierteiliger Name wie server.database.schema.table löst RuntimeError während der Metadatensuche aus, bevor irgendwelche Zeilen gesendet werden. Verbinden Sie sich direkt mit dem Zielserver und verwenden Sie einen einteiligen, zweiteiligen oder dreiteiligen Tabellennamen. Für Klammer-Escape und Fehler aufgrund ungültiger Namen siehe Namen von Zieltabellen.

Unicode oder Sekundenbruchteile gehen beim Massenkopieren verloren

Aktualisieren Sie den Treiber, falls die Massenkopie den UTF-8-Text beschädigt oder Bruchteile von Sekunden beim Laden kompatibler datetime2-Werte abschneidet. Die aktuelle Bulk Copy-Laufzeit verarbeitet UTF-8-Text und bewahrt die in den Quell- und Zieltypen verfügbare datetime2-Genauigkeit.

Verwenden Sie nvarchar oder eine UTF-8-Sortierung auf Varchar für Unicode-Texte. Python-Werte datetime und die Arrow-Fetch-Methoden des Treibers sind auf Mikrosekunden begrenzt. Für 100-Nanosekunden-Werte, die bereits in einer Arrow-Nanosekunden-Zeitstempelspalte gespeichert sind, laden Sie sie in datetime2(7), ohne sie vorher in Python datetime umzuwandeln. Weitere Informationen finden Sie unter Unicode und temporale Präzision sowie Arrow-Zeitgenauigkeit.

Verstöße gegen Einschränkungen während des Bulk Copy-Vorgangs

Symptome:

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

Ursache:

Die Daten in Ihrem Batch verstoßen gegen Tabellenbedingungen wie Primärschlüssel-, Unique-, Check- oder Fremdschlüssel-Constraints.

Solution:

Validiere die Daten, bevor du es lädst. Für große Datensätze laden Sie die Daten in eine Staging-Tabelle und führen Sie sie dann in das Ziel ein:

cursor.execute("CREATE TABLE ##Staging (ID INT, Name NVARCHAR(100))")
cursor.bulkcopy("##Staging", rows)

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

# Insert rows that don't exist in the target.
cursor.execute("""
    INSERT INTO dbo.Target (ID, Name)
    SELECT s.ID, s.Name
    FROM ##Staging AS s
    WHERE NOT EXISTS (
        SELECT 1
        FROM dbo.Target AS t
        WHERE t.ID = s.ID
    )
""")
conn.commit()

Informationen zu Upsert-Mustern mit Stagingtabellen finden Sie unter Muster für das Laden und Verschieben von Daten.

Spaltenabbildungsfehler

Symptome:

RuntimeError: Bulk copy failure - column count mismatch

Ursache:

Die Anzahl der Spalten in deinen Daten entspricht nicht der Anzahl der Kolonnen in der Zieltabelle, oder die Spalten sind in der falschen Reihenfolge.

Solution:

Stellen Sie sicher, dass Ihre Daten in der Reihenfolge und Anzahl mit dem Tabellenschema übereinstimmen:

from decimal import Decimal

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)

rows = [
    (1, "Widget", Decimal("19.99")),
    (2, "Gadget", Decimal("29.99")),
]
cursor.bulkcopy("dbo.MyTable", rows)

Typabweichungen beim Massenkopieren

Symptome:

Die Daten werden geladen, aber die Werte sind gekürzt, gerundet oder falsch.

Ursache:

Python-Werte werden nicht sauber auf die Ziel-Spaltentypen abgebildet. Gängige Beispiele sind float Werte, die in Dezimalspalten geladen werden und an Präzision verlieren können, sowie übergroße Strings, die in feste Spalten geladen werden.

Solution:

Verwenden Sie Python-Typen, die zu Ihrem Schema passen:

from decimal import Decimal

rows = [
    # Use Decimal for decimal and numeric columns.
    (1, "Widget", Decimal("19.99")),
    # Avoid float values because they can lose precision.
    # (1, "Widget", 19.99),
]
cursor.bulkcopy("dbo.Products", rows)

NumPy-Typbindungsfehler

Symptome:

Parameter schlagen lautlos fehl oder verursachen Datentypfehler, wenn du NumPy-Ganzzahl- oder Gleitmein-Typen verwendest.

Ursache:

NumPy-Typen wie numpy.int64 und numpy.int32 bestehen in NumPy 2.x isinstance(x, int) nicht. Die Typinferenz des Fahrers erkennt sie nicht, was zu unerwartetem Verhalten führt.

Solution:

Konvertiere NumPy-Werte in native Python-Typen, bevor du sie bindest:

import numpy as np

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

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"]),
        },
    )

Für größere Datensätze verwenden Sie die Arrow- oder Pandas-Integrationspfade. Diese Pfade übernehmen die Typumwandlung intern.

Massenkopieren mit temporären Tabellen

Symptome:

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

Ursache:

bulkcopy() kann temporäre Sitzungstabellen (#tablename) aufgrund von Einschränkungen bei der Metadatensuche nicht auflösen. Globale temporäre Tabellen (##tablename) und permanente Tabellen funktionieren.

Solution:

Verwenden Sie eine globale temporäre Tabelle oder eine reguläre Stagingtabelle:

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

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

Für kleine Datensätze, bei denen Sie eine temporäre Sitzungstabelle bevorzugen, verwenden Sie executemany():

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