Migreren van pyodbc naar mssql-python

De mssql-python-driver is Microsoft's eigen Python-driver voor Microsoft SQL. Als je liever een door Microsoft onderhouden driveroptie hebt, biedt het:

  • Geen externe ODBC-driverafhankelijkheid.
  • Ingebouwde verbindingspooling.
  • Ondersteuning voor moderne Python 3.10+-versies.
  • Ingebouwde Microsoft Entra-authenticatie.

Belangrijkste verschillen

Feature pyodbc mssql-python
Parameterstijl qmark (?) qmark (?) en pyformat (%(name)s)
ODBC-bestuurder vereist Yes Nee.
Groepsgewijze verbindingen External Built-in
Minimale Python-versie 3.6 3.10
callproc() Supported Niet geïmplementeerd
Autocommit standaardinstelling Off Off

Basisstappen voor migratie

De volgende stappen behandelen de sleutelwijzigingen om een pyodbc-applicatie te migreren naar mssql-python.

1. Werk importinstructies bij

Vervang de pyodbc import door mssql_python:

Voorheen (pyodbc):

import pyodbc

Na (mssql-python):

import mssql_python

2. Verbindingsreeksen bijwerken

Verwijder het DRIVER= trefwoord en werk de authenticatiemethode bij:

Daarvoor (pyodbc, een ODBC-stuurprogramma vereist):

conn = pyodbc.connect(
    "DRIVER={ODBC Driver 18 for SQL Server};"
    "SERVER=localhost;"
    "DATABASE=AdventureWorks2022;"
    "Trusted_Connection=yes;"
)

Daarna (mssql-python, geen driver nodig, gebruikmakend van Microsoft Entra-authenticatie):

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

3. Houd je vragen ongewijzigd

De mssql-python-driver ondersteunt zowel ? (qmark) als %(name)s (pyformat) parameterstijlen. Je bestaande ? queries werken zonder wijzigingen:

Daarvoor (pyodbc):

cursor.execute("SELECT * FROM Production.Product WHERE ProductID = ? AND Color = ?", (1, "Red"))

Na (mssql-python, dezelfde query):

cursor.execute("SELECT * FROM Production.Product WHERE ProductID = ? AND Color = ?", (1, "Red"))

4. Laat executemany ongewijzigd

Bestaande executemany aanroepen met tuples en ? markeringen werken zonder wijzigingen:

Daarvoor (pyodbc):

cursor.execute("CREATE TABLE #MigrateDemo (ID INT, Name NVARCHAR(50))")
data = [(1, "Alice"), (2, "Bob"), (3, "Carol")]
cursor.executemany("INSERT INTO #MigrateDemo (ID, Name) VALUES (?, ?)", data)

Na (mssql-python, dezelfde code):

cursor.execute("DROP TABLE IF EXISTS #MigrateDemo")
cursor.execute("CREATE TABLE #MigrateDemo (ID INT, Name NVARCHAR(50))")
data = [(1, "Alice"), (2, "Bob"), (3, "Carol")]
cursor.executemany("INSERT INTO #MigrateDemo (ID, Name) VALUES (?, ?)", data)

Migratie van opgeslagen procedures

De mssql-python-driver implementeert callproc()niet . De volgende secties laten zien hoe je het in plaats daarvan kunt gebruiken EXECUTE .

Gebruik EXECUTE voor opgeslagen procedures

De pyodbc-driver ondersteunt callproc(), maar de mssql-python-driver niet. Gebruik EXECUTE in plaats daarvan:

Daarvoor (pyodbc):

cursor.callproc("dbo.uspGetEmployeeManagers", (5,))
results = cursor.fetchall()

Na (mssql-python):

cursor.execute(
    "EXECUTE dbo.uspGetEmployeeManagers @BusinessEntityID = %(id)s",
    {"id": 5}
)
results = cursor.fetchall()
print(f"Got {len(results)} rows")

Uitvoerparameters

Gebruik T-SQL-variabelen om outputwaarden vast te leggen in plaats van te vertrouwen op callproc() outputparameters:

Daarvoor (pyodbc, met callproc):

params = (category_id, pyodbc.SQL_INTEGER)
cursor.callproc("dbo.GetProductCount", params)
count = params[1].value

Daarna (mssql-python, met T-SQL-variabelen):

cursor.execute(
    """
    DECLARE @count INT;
    SELECT @count = COUNT(*) FROM Production.Product
    WHERE ProductSubcategoryID = %(cat_id)s;
    SELECT @count AS ProductCount;
    """,
    {"cat_id": 1}
)
product_count = cursor.fetchval()
print(f"Product count: {product_count}")

Migraties voor specifieke functies

De volgende secties behandelen specifieke pyodbc-functies en hun mssql-python-equivalenten.

Aansluitstrengen

Pyodbc-trefwoord mssql-python sleutelwoord Aantekeningen
DRIVER={...} Niet nodig De ODBC-driver wordt intern gebundeld.
SERVER= Server= Geen gedragswijziging.
DATABASE= Database= Geen gedragswijziging.
Trusted_Connection= Trusted_Connection= Geen gedragswijziging.
UID= / PWD= UID= / PWD= Geen gedragswijziging.
Authentication= Authentication= Accepteert dezelfde waarden.

Autocommit

Het autocommit-gedrag is hetzelfde in beide stuurprogramma's:

Pyodbc:

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

mssql-python:

conn.autocommit = True

Bulk-invoegingen

Om grote batches van INSERT te versnellen, stellen pyodbc-gebruikers fast_executemany = True in. De mssql-python-driver optimaliseert executemany al voor geparametriseerde batches, dus matige inserts hebben geen speciale vlag nodig. Voor grote databelastingen geef je voorkeur aan bulkcopy(), wat rijen over het bulk-copy protocol streamt en veel sneller is dan het uitgeven van individuele INSERT statements. Voor de volledige workflow, zie Gebruik bulk copy.

Pyodbc:

cursor.fast_executemany = True
cursor.executemany(query, data)

Na (mssql-python), batches beperken met executemany:

cursor.execute("DROP TABLE IF EXISTS #BulkTarget")
cursor.execute("CREATE TABLE #BulkTarget (ID INT, Name NVARCHAR(50))")
data = [(i, f"Item {i}") for i in range(100)]
cursor.executemany("INSERT INTO #BulkTarget (ID, Name) VALUES (?, ?)", data)
conn.commit()

Na (mssql-python), bij hoge belasting met bulkcopy (aanbevolen):

cursor.execute("IF OBJECT_ID('##BulkTarget') IS NOT NULL DROP TABLE ##BulkTarget")
cursor.execute("CREATE TABLE ##BulkTarget (ID INT, Name NVARCHAR(50))")
conn.commit()  # Commit DDL before bulkcopy
data = [(i, f"Item {i}") for i in range(100)]
result = cursor.bulkcopy("##BulkTarget", data)
print(f"Bulk copied {result['rows_copied']} rows")
cursor.execute("DROP TABLE ##BulkTarget")
conn.commit()

Rijfabriek

De mssql-python-driver retourneert standaard Row-objecten die toegang via attributen ondersteunen, zonder dat hiervoor een aangepaste row factory nodig is:

Pyodbc (Custom Row Factory):

def namedtuple_row_factory(cursor):
    from collections import namedtuple
    columns = [col[0] for col in cursor.description]
    Row = namedtuple("Row", columns)
    return Row

mssql-python (standaard attribuuttoegang):

cursor.execute("SELECT Name, ListPrice FROM Production.Product")
row = cursor.fetchone()
print(row.Name)   # Attribute access works directly
print(row[0])     # Index access also works

Foutafhandeling

De mssql-python-driver gebruikt dezelfde uitzonderingshiërarchie als pyodbc, dus de meeste uitzonderingshandlers vereisen alleen een modulenaamwijziging.

Uitzonderingshiërarchie

De namen van de uitzonderingsklassen komen rechtstreeks overeen tussen drivers:

Pyodbc:

try:
    cursor.execute(query)
except pyodbc.Error as e:
    pass
except pyodbc.DatabaseError as e:
    pass
except pyodbc.OperationalError as e:
    pass

mssql-python:

try:
    cursor.execute("SELECT TOP 1 * FROM Production.Product")
    print(cursor.fetchone())
except mssql_python.Error as e:
    pass
except mssql_python.DatabaseError as e:
    pass
except mssql_python.OperationalError as e:
    pass

Foutdetails

Beide drivers geven foutdetails bloot via uitzonderingsargumenten:

Pyodbc:

try:
    cursor.execute(query)
except pyodbc.Error as e:
    sqlstate = e.args[0]
    message = e.args[1]

mssql-python:

try:
    cursor.execute("SELECT TOP 1 * FROM NonExistentTable_XYZ")
except mssql_python.Error as e:
    # Error message contains SQLSTATE and details
    print(str(e))

Groepsgewijze verbindingen

De mssql-python-driver bevat standaard verbindingspooling, waardoor externe poolingbibliotheken niet langer nodig zijn.

Verwijder externe pooling

Als je externe pooling met pyodbc hebt gebruikt, heeft de mssql-python-driver dit ingebouwd:

Daarvoor (pyodbc externe pool):

from dbutils.pooled_db import PooledDB

pool = PooledDB(pyodbc, 5, driver="{ODBC Driver 18 for SQL Server}",
                server="your_server", database="your_database",
                uid="your_username", pwd="your_password")
conn = pool.connection()

Na (mssql-python ingebouwde pooling):

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

Pool configureren

Overschrijf de standaard poolgrootte en timeout met mssql_python.pooling():

import mssql_python

mssql_python.pooling()

Voorbeeld van volledige migratie

Het volgende toont dezelfde functie geschreven met pyodbc en vervolgens herschreven met mssql-python.

Daarvoor (pyodbc)

Deze versie gebruikt de pyodbc-verbindingsreeks met een DRIVER trefwoord:

import pyodbc
from datetime import date

def get_orders(customer_id: int, start_date: date):
    conn = pyodbc.connect(
        "DRIVER={ODBC Driver 18 for SQL Server};"
        "SERVER=localhost;"
        "DATABASE=AdventureWorks2022;"
        "Trusted_Connection=yes;"
    )
    cursor = conn.cursor()

    cursor.execute("""
        SELECT SalesOrderID, OrderDate, TotalDue
        FROM Sales.SalesOrderHeader
        WHERE CustomerID = ? AND OrderDate >= ?
        ORDER BY OrderDate DESC
    """, (customer_id, start_date))

    orders = []
    for row in cursor:
        orders.append({
            "id": row.SalesOrderID,
            "date": row.OrderDate,
            "total": row.TotalDue
        })

    cursor.close()
    conn.close()
    return orders

Na (mssql-python)

Deze versie verwijdert het DRIVER trefwoord. Alle queries, parameters en toegangspatronen voor rijen blijven identiek:

import mssql_python
from datetime import date

def get_orders(customer_id: int, start_date: date):
    conn = mssql_python.connect(
        "Server=localhost;"
        "Database=AdventureWorks2022;"
        "Trusted_Connection=yes;"
    )
    cursor = conn.cursor()

    cursor.execute("""
        SELECT SalesOrderID, OrderDate, TotalDue
        FROM Sales.SalesOrderHeader
        WHERE CustomerID = ? AND OrderDate >= ?
        ORDER BY OrderDate DESC
    """, (customer_id, start_date))

    orders = []
    for row in cursor:
        orders.append({
            "id": row.SalesOrderID,
            "date": row.OrderDate,
            "total": row.TotalDue
        })

    cursor.close()
    conn.close()
    return orders

De enige wijzigingen zijn de import-instructie en de verbindingsreeks (geen DRIVER trefwoord nodig). Elke query, parameter, fetch-patroon en rijtoegang blijft identiek.

Migratie testen

Voordat je de migratie voltooit, voer je dezelfde zoekopdrachten uit op beide drivers en vergelijk je de resultaten om gelijkwaardig gedrag te bevestigen.

Verifieer gelijkwaardig gedrag

Gebruik een vergelijkingsfunctie die dezelfde query uitvoert op beide drivers en de resultaatmatch bevestigt:

import pyodbc
import mssql_python

def compare_results(pyodbc_conn_str: str, mssql_conn_str: str, query: str):
    """Compare results from both drivers."""
    # pyodbc query
    pyodbc_conn = pyodbc.connect(pyodbc_conn_str)
    pyodbc_cursor = pyodbc_conn.cursor()
    pyodbc_cursor.execute(query)
    pyodbc_results = pyodbc_cursor.fetchall()
    pyodbc_conn.close()
    
    # mssql-python query
    mssql_conn = mssql_python.connect(mssql_conn_str)
    mssql_cursor = mssql_conn.cursor()
    mssql_cursor.execute(query)
    mssql_results = mssql_cursor.fetchall()
    mssql_conn.close()
    
    # Compare
    assert len(pyodbc_results) == len(mssql_results)
    for p_row, m_row in zip(pyodbc_results, mssql_results):
        assert tuple(p_row) == tuple(m_row)
    
    print(f"Results match: {len(pyodbc_results)} rows")

Controlelijst

  • [ ] Werk de importen bij van pyodbc naar mssql_python.
  • [ ] Verwijder DRIVER= van verbindingslijnen.
  • [ ] Behoud bestaande ? parameterqueries (ze werken as-is).
  • [ ] Gebruik EXECUTE instructies voor aanroepen van opgeslagen procedures.
  • [ ] Verwijder de externe verbindingspoolconfiguratie.
  • [ ] Update klassennamen voor het afhandelen van uitzonderingen.
  • [ ] Test alle zoekopdrachten en opgeslagen procedures.
  • [ ] Controleer de verwerking van gegevenstypen (vooral decimalen en data).
  • [ ] Verwijder de ODBC-driver uit de implementatievereisten.