Migracja z pymssql 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 zależności od FreeTDS.
  • Wiele współbieżnych kursorów na połączenie.
  • Wbudowane łączenie połączeń.
  • Nowoczesne wsparcie dla Python 3.10+.
  • Natywne uwierzytelnianie Microsoft Entra.
  • Domyślnie obiekty wierszowe z dostępem do atrybutów.

Podstawowe różnice

Funkcja pymssql mssql-python
Styl parametrów format (%s, %d) qmark (?) oraz pyformat (%(name)s)
Rodzima biblioteka FreeTDS DDBC (w zestawie)
Buforowanie połączeń Zewnętrzne Built-in
Kursory na każde połączenie 1 Wielokrotny
Minimalna wersja języka Python 3.6 3.10
callproc() Wsparte Nie zaimplementowano
as_dict kursor Extension Obiekty wierszowe (domyślnie)
Kopiowanie zbiorcze conn.bulk_copy() cursor.bulkcopy()
Domyślne ustawienie automatycznego zatwierdzania Off Off

Podstawowe kroki migracji

Poniższe kroki przedstawiają najczęstsze zmiany potrzebne do migracji aplikacji pymssql do mssql-python.

1. Aktualizuj importy

Zastąp import pymssql elementem mssql_python:

Przed (pymssql):

import pymssql

Po (mssql-python):

import mssql_python

2. Aktualizacja wywołań połączeń

PymsSQL używa argumentów pozycyjnych. Sterownik mssql-python wykorzystuje parametry połączenia lub argumenty słów kluczowych:

Przed (pymssql, argumenty pozycyjne):

conn = pymssql.connect("<server>", "<user>", "<password>", "<database>")

Przed (pymssql, argumenty słów kluczowych):

conn = pymssql.connect(
    host=r"<server>\<instance>",
    user="<login>",
    password="<password>",
    database="<database>"
)

Po (mssql-python, parametry połączenia):

conn = mssql_python.connect(
    "Server=<server>;"
    "Database=<database>;"
    "UID=<username>;"
    "PWD=<password>;"
    "Encrypt=yes;"
)

Po (mssql-python, zalecane: Microsoft Entra):

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

3. Aktualizuj markery parametrów

pymssql używa symboli zastępczych formatu %s i %d. Sterownik mssql-python używa ? (qmark) lub %(name)s (pyformat):

Przed (pymssql):

cursor.execute("SELECT * FROM Person.Person WHERE BusinessEntityID = %d AND FirstName = %s", (user_id, name))

After (mssql-python, styl qmark):

user_id, name = 1, "Ken"
cursor.execute("SELECT * FROM Person.Person WHERE BusinessEntityID = ? AND FirstName = ?", (user_id, name))
print(cursor.fetchone())

cursor.execute(
    "SELECT * FROM Person.Person WHERE BusinessEntityID = %(id)s AND FirstName = %(name)s",
    {"id": user_id, "name": name}
)
print(cursor.fetchone())

4. Zaktualizuj executemany

Zaktualizuj symbole zastępcze SQL z %s/%d na ? lub %(name)s:

Przed (pymssql):

cursor.execute("CREATE TABLE #Persons (ID INT, Name NVARCHAR(50), Department NVARCHAR(50))")
cursor.executemany(
    "INSERT INTO #Persons VALUES (%d, %s, %s)",
    [(1, "John", "Sales"), (2, "Jane", "Marketing")]
)

After (mssql-python, styl qmark):

cursor.execute("IF OBJECT_ID('#Persons') IS NOT NULL DROP TABLE #Persons")
cursor.execute("CREATE TABLE #Persons (ID INT, Name NVARCHAR(50), Department NVARCHAR(50))")
cursor.executemany(
    "INSERT INTO #Persons VALUES (?, ?, ?)",
    [(1, "John", "Sales"), (2, "Jane", "Marketing")]
)
cursor.execute("SELECT * FROM #Persons")
for row in cursor:
    print(row)

5. Używaj atrybutów wiersza zamiast as_dict kursorów

pymssql wymaga as_dict=True, aby uzyskać dostęp do kolumn według nazw. Sterownik mssql-python domyślnie zwraca Row obiekty obsługujące zarówno dostęp do atrybutów, jak i indeksów:

Przed (pymssql):

cursor = conn.cursor(as_dict=True)
cursor.execute("SELECT BusinessEntityID, FirstName FROM Person.Person WHERE FirstName = %s", ("John",))
for row in cursor:
    print("ID=%d, Name=%s" % (row["BusinessEntityID"], row["FirstName"]))

Po (mssql-python, domyślny dostęp do atrybutów):

cursor = conn.cursor()
cursor.execute("SELECT BusinessEntityID, FirstName FROM Person.Person WHERE FirstName = ?", ("John",))
for row in cursor:
    print(f"ID={row.BusinessEntityID}, Name={row.FirstName}")
    # Index access also works: row[0], row[1]

Migracja procedur przechowywanych

Sterownik mssql-python nie implementuje callproc(). Zamiast tego używaj instrukcji EXECUTE.

Użyj EXECUTE dla procedur przechowywanych

PymsSQL obsługuje callproc(), ale sterownik MSSQL-Python nie. Użyj EXECUTE zamiast tego:

Przed (pymssql):

cursor.callproc("uspGetEmployeeManagers", (5,))
for row in cursor:
    print(row)

Po (mssql-python):

cursor.execute("EXECUTE dbo.uspGetEmployeeManagers @BusinessEntityID = ?", (5,))
for row in cursor:
    print(row)

Parametry wyjściowe

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

Przed (pymssql):

cursor.callproc("GetProductCount", (category_id,))
count = cursor.fetchval()

Po (mssql-python, zmienne T-SQL):

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

Migracja kopii masowych

pymssql wywołuje bulk_copy() dla połączenia. Sterownik mssql-python wywołuje bulkcopy() na kursorze z dodatkowymi opcjami:

Przed (pymssql):

conn.bulk_copy("##BulkDemo", [(1, 2)] * 1000)
conn.commit()

Po (mssql-python):

cursor = conn.cursor()
cursor.execute("CREATE TABLE ##BulkDemo (Col1 INT, Col2 INT)")
conn.commit()
result = cursor.bulkcopy("##BulkDemo", [(1, 2)] * 1000)
print(f"Copied {result['rows_copied']} rows")
conn.commit()
cursor.execute("DROP TABLE ##BulkDemo")
conn.commit()

Metoda mssql-python bulkcopy() obsługuje batch_size, timeout, column_mappings, keep_identitycheck_constraints, table_lock, , keep_nulls, , fire_triggersoraz .use_internal_transaction Zobacz kopię zbiorczą dla szczegółów.

Wiele kursorów

pymssql pozwala tylko na jeden aktywny kursor na połączenie. Sterownik mssql-python obsługuje wiele równoczesnych kursorów:

Przed (pymssql):

c1 = conn.cursor()
c1.execute("SELECT TOP 5 * FROM Person.Person")
c2 = conn.cursor()
c2.execute("SELECT TOP 5 * FROM Sales.SalesOrderHeader")
c1.fetchall()

Po (mssql-python):

c1 = conn.cursor()
c1.execute("SELECT TOP 5 BusinessEntityID, FirstName FROM Person.Person")
persons = c1.fetchall()

c2 = conn.cursor()
c2.execute("SELECT TOP 5 SalesOrderID FROM Sales.SalesOrderHeader")
orders = c2.fetchall()
print(f"Persons: {len(persons)}, Orders: {len(orders)}")

Buforowanie połączeń

PymsSQL nie ma wbudowanego poolingu. Sterownik mssql-python zawiera go automatycznie:

Przed (pymssql, wymagana zewnętrzna pula):

from dbutils.pooled_db import PooledDB
pool = PooledDB(pymssql, host="server", user="user", password="pwd", database="db")
conn = pool.connection()

Po (mssql-python, pulowanie jest automatyczne):

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

Obsługa błędów

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

Przed (pymssql):

try:
    cursor.execute(query)
except pymssql.OperationalError as e:
    print(f"Operation failed: {e}")
except pymssql.InterfaceError as e:
    print(f"Interface error: {e}")

Po (mssql-python):

try:
    cursor = conn.cursor()
    cursor.execute("SELECT TOP 1 * FROM Production.Product")
    row = cursor.fetchone()
    print(row)
except mssql_python.OperationalError as e:
    print(f"Operation failed: {e}")
except mssql_python.InterfaceError as e:
    print(f"Interface error: {e}")

Kompletny przykład migracji

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

Przed (pymssql)

Ta wersja wykorzystuje argumenty pozycyjne funkcji connect oraz znaczniki parametrów as_dict=True i %d:

import pymssql

def get_orders(customer_id: int):
    conn = pymssql.connect("<server>", "<user>", "<password>", "<database>")
    cursor = conn.cursor(as_dict=True)

    cursor.execute("""
        SELECT TOP 10 SalesOrderID, OrderDate, TotalDue
        FROM Sales.SalesOrderHeader
        WHERE CustomerID = %d
        ORDER BY OrderDate DESC
    """, (customer_id,))

    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)

Kluczowe zmiany strukturalne to znaczniki parametrów, styl połączenia oraz dostęp do wierszy:

import mssql_python

def get_orders(customer_id: int):
    conn = mssql_python.connect(
        "Server=<server>;"
        "Database=<database>;"
        "UID=<username>;"
        "PWD=<password>;"
    )
    cursor = conn.cursor()

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

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

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

Zmiany strukturalne to:

  1. import pymssqlimport mssql_python.
  2. Pozycyjne argumenty połączenia → parametry połączenia ze słowami kluczowymi.
  3. %d Marker parametrów → ?.
  4. cursor(as_dict=True)cursor() z dostępem do atrybutów (row.SalesOrderID zamiast row["SalesOrderID"]).

Lista kontrolna

  • [ ] Aktualizuj importy z pymssql do .mssql_python
  • [ ] Przekonwertuj wywołania połączeń z argumentów pozycyjnych na ciągi połączeń.
  • [ ] Przekonwertuj %s/%dznaczniki parametrów na ? lub %(name)s.
  • [ ] Używaj EXECUTE instrukcji do wywołań procedur przechowywanych.
  • [ ] Używaj Row dostępu do atrybutów zamiast as_dict=True kursorów.
  • [ ] Migruj conn.bulk_copy() do cursor.bulkcopy().
  • [ ] Usuń konfigurację pulowania połączeń zewnętrznych.
  • [ ] Usuń FreeTDS z wymagań wdrożenia.
  • [ ] Aktualizuj nazwy klas obsługujących wyjątki.
  • [ ] Testuj wszystkie zapytania i procedury przechowywane.