Migrera från pyodbc till mssql-python

MSSQL-python-drivrutinen är Microsoft förstaparts Python-drivrutin för Microsoft SQL. Om du föredrar ett drivrutinsalternativ som underhålls av Microsoft erbjuds det:

  • Inget beroende av extern ODBC-drivrutin.
  • Inbyggd anslutningspooling.
  • Stöd för modern Python 3.10+
  • Inbyggd Microsoft Entra-autentisering.

Viktiga skillnader

Feature pyodbc mssql-python
Parameterstil qmark (?) qmark (?) och pyformat (%(name)s)
ODBC-förare krävs Ja No
Anslutningspoolning Externt Inbyggd
Minsta Python-version 3.6 3.10
callproc() Stöds Inte implementerad
Standard för automatisk incheckning Off Off

Grundläggande migrationssteg

Följande steg täcker nyckeländringarna för att migrera en pyodbc-applikation till mssql-python.

1. Uppdatera importen

Ersätt importen pyodbc med mssql_python:

Före (pyodbc):

import pyodbc

Efter (mssql-python):

import mssql_python

2. Uppdatera anslutningssträngar

Ta bort DRIVER= nyckelordet och uppdatera autentiseringsmetoden:

Innan (pyodbc, kräver ODBC-drivrutin):

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

Efter (mssql-python, ingen drivrutin behövs, använder Microsoft Entra-autentisering):

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

3. Behåll dina frågor som de är

mssql-python-drivrutinen stöder både ? (qmark) och %(name)s (pyformat) parameterstilar. Dina befintliga ? frågor fungerar utan ändringar:

Före (pyodbc):

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

Efter (mssql-python, samma fråga):

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

4. Behåll executemany som det är

Befintliga executemany anrop med tupler och ? markörer fungerar utan ändringar:

Före (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)

Efter (mssql-python, samma kod):

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)

Migrering av lagrad procedur

mssql-python-drivrutinen implementerar inte callproc(). Följande avsnitt visar hur man i stället använder EXECUTE.

Använd EXECUTE för lagrade procedurer

Pyodbc-drivrutinen stödjer callproc(), men mssql-python-drivrutinen gör det inte. Använd EXECUTE i stället:

Före (pyodbc):

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

Efter (mssql-python):

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

Utdataparametrar

Använd T-SQL-variabler för att fånga utdatavärden istället för att förlita dig på callproc() utdataparametrar:

Innan (pyodbc, använder callproc):

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

Efter (mssql-python, med T-SQL-variabler):

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

Migreringar för specifika funktioner

Följande avsnitt täcker specifika pyodbc-funktioner och deras motsvarigheter till mssql-python.

Anslutningssträngar

pyodbc-nyckelord mssql-python-nyckelordet Noteringar
DRIVER={...} Behövs inte ODBC-drivrutinen är paketerad internt.
SERVER= Server= Inget beteende ändras.
DATABASE= Database= Inget beteende ändras.
Trusted_Connection= Trusted_Connection= Inget beteende ändras.
UID= / PWD= UID= / PWD= Inget beteende ändras.
Authentication= Authentication= Accepterar samma värden.

Autocommit

Beteendet för autocommit är identiskt i båda drivrutinerna:

Pyodbc:

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

mssql-python:

conn.autocommit = True

Bulkinsatser

För att snabba upp stora INSERT batcher sätter pyodbc-användare in fast_executemany = True. mssql-python-drivrutinen optimerar executemany redan för parameteriserade batcher, så måttliga inserts behöver ingen särskild flagga. För stora datalaster, föredra bulkcopy(), vilket strömmar rader över bulkkopieringsprotokollet och är mycket snabbare än att utfärda enskilda INSERT uttalanden. För hela arbetsflödet, se Använd bulkkopiering.

Pyodbc:

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

Efter (mssql-python), moderera batchar med 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()

Efter (mssql-python), stora laster med bulkcopy (föredras):

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

Radfabrik

mssql-python-drivrutinen returnerar Row objekt som stöder attributåtkomst som standard, utan att kräva en anpassad radfabrik:

pyodbc (anpassad radgenerator):

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 (attributåtkomst som standard):

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

Felhantering

mssql-python-drivrutinen använder samma undantagshierarki som pyodbc, så de flesta undantagshanterare kräver endast ett modulnamnbyte.

Undantagshierarki

Undantagsklassnamnen mappas direkt mellan drivrutinerna:

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

Felinformation

Båda drivrutinerna exponerar feldetaljer genom undantagsargument:

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

Anslutningspoolning

mssql-python-drivrutinen inkluderar anslutningspooling som standard, så externa poolingbibliotek behövs inte längre.

Ta bort extern sammanslagning

Om du använde extern pooling med pyodbc har mssql-python-drivrutinen det inbyggt:

Före (pyodbc extern 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()

Efter (mssql-python inbyggd poolning):

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

Konfigurera pool

Överstyr standardpoolstorleken och timeout med mssql_python.pooling():

import mssql_python

mssql_python.pooling()

Exempel på fullständig migrering

Följande visar samma funktion skriven med pyodbc och sedan omskriven med mssql-python.

Före (pyodbc)

Denna version använder pyodbc-reťazec pripojenia med ett DRIVER nyckelord:

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

Efter (mssql-python)

Denna version tar bort DRIVER nyckelordet. Alla frågor, parametrar och radåtkomstmönster förblir identiska:

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 enda ändringarna är importsatsen och reťazec pripojenia (inget DRIVER nyckelord behövs). Varje fråga, parameter, hämtamönster och radåtkomst förblir identisk.

Testa migrering

Innan du slutför migreringen, kör samma frågor mot båda drivrutinerna och jämför resultaten för att bekräfta motsvarande beteende.

Verifiera ekvivalent beteende

Använd en jämförelsefunktion som kör samma fråga mot båda drivrutinerna och bekräftar resultatmatchningen:

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

Checklista

  • [ ] Uppdatera importer från pyodbc till mssql_python.
  • [ ] Ta bort DRIVER= från anslutningssträngar.
  • [ ] Behåll befintliga ? parameterfrågor (de fungerar as-is).
  • [ ] Använd EXECUTE instruktioner vid anrop av lagrade procedurer.
  • [ ] Ta bort konfigurationen för extern anslutningspooling.
  • [ ] Uppdatera klassnamn för hantering av undantag.
  • [ ] Testa alla frågor och lagrade procedurer.
  • [ ] Verifiera hantering av datatyper (särskilt decimaler och datum).
  • [ ] Ta bort ODBC-drivrutinen från distributionskraven.