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

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

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

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

Für Upsertmuster mit Staging-Tabellen siehe 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,
)