Migracja z pyodbc do mssql-python

Sterownik mssql-python to pierwszy oficjalny sterownik Python firmy Microsoft do Microsoft SQL. Jeśli wolisz opcję sterownika utrzymywanego przez Microsoft, oferuje:

  • Brak zewnętrznej zależności sterownika ODBC.
  • Wbudowane łączenie połączeń.
  • Nowoczesne wsparcie dla Python 3.10+.
  • Natywne uwierzytelnianie Microsoft Entra.

Podstawowe różnice

Funkcja pyodbc mssql-python
Styl parametrów qmark (?) qmark (?) oraz pyformat (%(name)s)
Wymagany sterownik ODBC Yes No
Buforowanie połączeń Zewnętrzne Built-in
Minimalna wersja języka Python 3.6 3.10
callproc() Wsparte Nie zaimplementowano
Domyślne ustawienie automatycznego zatwierdzania Off Off

Podstawowe kroki migracji

Poniższe kroki obejmują zmiany klucza niezbędne do migracji aplikacji pyodbc do mssql-python.

1. Aktualizuj importy

Zastąp import pyodbc elementem mssql_python:

Przed (pyodbc):

import pyodbc

Po (mssql-python):

import mssql_python

2. Aktualizacja ciągów połączeń

Usuń DRIVER= słowo kluczowe i zaktualizuj metodę uwierzytelniania:

Wcześniej (pyodbc, wymaga sterownika ODBC):

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

Po (mssql-python, bez potrzeby sterownika, przy użyciu uwierzytelniania Microsoft Entra):

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

3. Pozostaw swoje zapytania bez zmian

Sterownik mssql-python obsługuje oba style parametrów: ? (qmark) i %(name)s (pyformat). Twoje istniejące ? zapytania działają bez zmian:

Przed (pyodbc):

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

Po (mssql-python, to samo zapytanie):

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

4. Pozostaw executemany bez zmian

Dotychczasowe wywołania executemany z krotkami i znacznikami ? działają bez zmian:

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

Po (mssql-python, ten sam 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)

Migracja procedur przechowywanych

Sterownik mssql-python nie implementuje callproc(). W poniższych sekcjach pokazano, jak zamiast tego używać EXECUTE.

Użyj EXECUTE dla procedur przechowywanych

Sterownik pyodbc obsługuje callproc(), ale sterownik mssql-python nie. Użyj EXECUTE zamiast tego:

Przed (pyodbc):

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

Po (mssql-python):

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

Parametry wyjściowe

Używaj zmiennych T-SQL do rejestrowania wartości wyjściowych zamiast polegać na parametrach callproc() wyjściowych:

Przed (pyodbc, używając callproc):

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

Po (mssql-python, używając zmiennych T-SQL):

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

Migracje specyficzne dla funkcji

Poniższe sekcje obejmują konkretne funkcje pyodbc oraz ich odpowiedniki mssql-python.

Łańcuchy połączenia

słowo kluczowe pyodbc Słowo kluczowe mssql-python Notatki
DRIVER={...} Nie jest wymagany Sterownik ODBC jest dołączony wewnętrznie.
SERVER= Server= Brak zmian zachowania.
DATABASE= Database= Brak zmian zachowania.
Trusted_Connection= Trusted_Connection= Brak zmian zachowania.
UID= / PWD= UID= / PWD= Brak zmian zachowania.
Authentication= Authentication= Akceptuje te same wartości.

Autozatwierdzanie

Zachowanie automatycznego zatwierdzania jest identyczne w obu sterownikach:

pyodbc:

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

mssql-python:

conn.autocommit = True

Wkładki zbiorcze

Aby przyspieszyć duże INSERT partie, użytkownicy pyodbc ustawiają fast_executemany = True. Sterownik mssql-python już optymalizuje executemany dla partii wsadowych z parametrami, więc umiarkowana liczba operacji wstawiania nie wymaga żadnej specjalnej flagi. Dla dużych ładowań danych preferujemy bulkcopy(), które przesyła wiersze przez protokół kopiowania masowego i jest znacznie szybsze niż wydawanie pojedynczych INSERT instrukcji. Pełny przepływ pracy znajdziesz w artykule Użyj kopii masowej.

pyodbc:

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

Po (mssql-python) umiarkuj partie z :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()

Po „mssql-python”, duże obciążenia z bulkcopy (zalecane):

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

Generator wierszy

Sterownik mssql-python zwraca obiekty Row, które domyślnie umożliwiają dostęp do atrybutów, bez konieczności użycia niestandardowej fabryki wierszy:

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 (domyślny dostęp do atrybutów):

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

Obsługa błędów

Sterownik mssql-python używa tej samej hierarchii wyjątków co pyodbc, więc większość obsługiwaczy wyjątków wymaga jedynie zmiany nazwy modułu.

Hierarchia wyjątków

Nazwy klas wyjątków odwzorowują się bezpośrednio między sterownikami:

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

Szczegóły błędu

Oba sterowniki ujawniają szczegóły błędów za pomocą argumentów wyjątków:

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

Buforowanie połączeń

Sterownik mssql-python domyślnie zawiera pulowanie połączeń, więc zewnętrzne biblioteki pulujące nie są już potrzebne.

Usuń zewnętrzne buforowanie

Jeśli używałeś zewnętrznej puli z pyodbc, sterownik mssql-python ma to wbudowane:

Przed (pyodbc external 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()

Po (wbudowane buforowanie połączeń mssql-python):

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

Konfiguruj pulę

Nadpisz domyślny rozmiar puli i limit czasu za pomocą mssql_python.pooling():

import mssql_python

mssql_python.pooling()

Kompletny przykład migracji

Poniżej przedstawiono tę samą funkcję napisaną w pyodbc, a następnie przepisaną w mssql-python.

Przed (pyodbc)

Ta wersja używa parametrów połączenia pyodbc ze słowem kluczowym DRIVER:

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

Po (mssql-python)

Ta wersja usuwa to DRIVER słowo kluczowe. Wszystkie zapytania, parametry i wzorce dostępu do wierszy pozostają identyczne:

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

Jedynymi zmianami są instrukcja importu i parametry połączenia (słowo kluczowe DRIVER nie jest potrzebne). Każde zapytanie, parametr, wzór pobierania i dostęp do wiersza pozostają identyczne.

Testowanie migracji

Przed zakończeniem migracji uruchom te same zapytania na obu sterownikach i porównaj wyniki, aby potwierdzić równoważne zachowanie.

Zweryfikować równoważne zachowanie

Użyj funkcji porównawczej, która wykonuje to samo zapytanie na obu sterownikach i potwierdza, że wyniki się zgadzają:

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

Lista kontrolna

  • [ ] Aktualizuj importy z pyodbc do .mssql_python
  • [ ] Usuń DRIVER= ze znaków połączeń.
  • [ ] Zachowuj istniejące ? zapytania parametrów (działają as-is).
  • [ ] Używaj EXECUTE instrukcji do wywołań procedur przechowywanych.
  • [ ] Usuń konfigurację pulowania połączeń zewnętrznych.
  • [ ] Aktualizuj nazwy klas obsługujących wyjątki.
  • [ ] Testuj wszystkie zapytania i procedury przechowywane.
  • [ ] Weryfikacja obsługi typów danych (szczególnie dziesiętnych i dat).
  • [ ] Usuń sterownik ODBC z wymagań wdrożenia.