Migration von SQLite zu Microsoft SQL mit mssql-python

SQLite ist die Standarddatenbank für viele Python-Projekte, FastAPI und Flask-Apps. Sie ermöglicht es Ihnen, Ihre Anwendung schnell zu erstellen, indem Sie die Entscheidung über die Datenplattform auf später verschieben. Wenn es Zeit ist, Ihre Anwendung in die Produktion zu bringen, benötigen Sie Unterstützung für gleichzeitige Benutzer, rollenbasierte Sicherheit, hohe Verfügbarkeit, Disaster Recovery und andere Unternehmensfunktionen. Du musst mit dem mssql-python-Treiber auf Microsoft SQL migrieren.

Unterschiede im SQL-Dialekt

Wenn du von SQLite zu Microsoft SQL migrierst, musst du zwei Dinge angehen: deine SQL-Anweisungen für Transact-SQL (T-SQL) umschreiben und deine Daten migrieren.

Die folgende Tabelle ordnet gängige SQLite-Muster ihren Microsoft SQL-Entsprechungen zu:

SQLite SQL Server (T-SQL) Hinweise
INTEGER PRIMARY KEY AUTOINCREMENT int IDENTITY(1,1) PRIMARY KEY Microsoft SQL verwendet IDENTITY für Autoinkrement.
TEXT nvarchar(255) oder nvarchar(max) Geben Sie immer eine Länge an. Verwenden Sie nvarchar für Unicode.
REAL float oder decimal(18,2) Verwenden Sie decimal für exakte Werte wie Geldbeträge.
BLOB varbinary(max) Dasselbe Verhalten, anderer Name.
BOOLEAN (gespeichert als INTEGER) bit Keine der beiden Datenbanken hat einen nativen Boolean. Beide speichern 0/1.
DATETIME('now') GETDATE() oder SYSDATETIME() SYSDATETIME() führt zu höherer Präzision.
LIMIT 10 OFFSET 20 OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY Erfordert eine ORDER BY Klausel.
\|\| (Saitenkonkat) + oder CONCAT() CONCAT() verarbeitet NULL Werte.
IFNULL(a, b) ISNULL(a, b) oder COALESCE(a, b) COALESCE ist ANSI-Standard.
GROUP_CONCAT(col) STRING_AGG(col, ',') Verfügbar in SQL Server 2017+.
INSERT OR REPLACE INTO MERGE-Anweisung SQLite löscht und fügt wieder ein; MERGE aktualisiert an Ort und Stelle. Siehe das folgende Beispiel.
last_insert_rowid() OUTPUT INSERTED.id Verwenden Sie OUTPUT in der Anweisung INSERT. SCOPE_IDENTITY() Funktioniert auch, erfordert aber einen separaten SELECT.
typeof(x) SQL_VARIANT_PROPERTY(x, 'BaseType') Selten nötig bei strenger Typisierung.

CREATE TABLE Beispiel

-- SQLite
CREATE TABLE IF NOT EXISTS products (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    price REAL DEFAULT 0.0,
    created_at TEXT DEFAULT (datetime('now')),
    is_active BOOLEAN DEFAULT 1
);

-- SQL Server
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'products')
CREATE TABLE products (
    id int IDENTITY(1,1) PRIMARY KEY,
    name nvarchar(100) NOT NULL,
    price decimal(10,2) DEFAULT 0.0,
    created_at datetime2 DEFAULT SYSDATETIME(),
    is_active bit DEFAULT 1
);

Abfragebeispiele

Paginierung:

SQLite:

cursor.execute("SELECT * FROM products ORDER BY name LIMIT ? OFFSET ?", (10, 20))

mssql-python:

cursor.execute(
    "SELECT * FROM Production.Product ORDER BY Name OFFSET ? ROWS FETCH NEXT ? ROWS ONLY",
    (20, 10)
)

Die Reihenfolge der Parameter ist umgekehrt. Transact-SQL setzt OFFSET vor FETCH NEXT.

Upsert (Einfügen oder Update):

SQLite:

cursor.execute("""
    INSERT OR REPLACE INTO settings (key, value)
    VALUES (?, ?)
""", (key, value))

mssql-python:

cursor.execute("""
    MERGE #Settings AS target
    USING (SELECT ? AS [key], ? AS value) AS source
    ON target.[key] = source.[key]
    WHEN MATCHED THEN UPDATE SET value = source.value
    WHEN NOT MATCHED THEN INSERT ([key], value) VALUES (source.[key], source.value);
""", (key, value))

Die Klausel USING definiert source.[key] und source.value als Spaltenaliase. Die Klauseln WHEN beziehen sich auf diese Aliasnamen. Es werden nur zwei ? Marker benötigt.

Zuletzt eingefügte ID:

SQLite:

cursor.execute("INSERT INTO products (name) VALUES (?)", ("Widget",))
product_id = cursor.lastrowid

mssql-python:

cursor.execute(
    "INSERT INTO #Products (Name) OUTPUT INSERTED.ProductID VALUES (?)",
    ("Widget",)
)
product_id = cursor.fetchval()

Verbindungscode aktualisieren

Ersetzen Sie sqlite3.connect() durch mssql_python.connect():

SQLite:

import sqlite3

def get_connection():
    conn = sqlite3.connect("myapp.db")
    conn.row_factory = sqlite3.Row
    return conn

mssql-python:

import mssql_python

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

Der Zugriff auf Zeilen funktioniert ähnlich. Die SQLite-Fabrik Row liefert dict-ähnliche Zeilen mit row["column"] Syntax zurück. Der mssql-python-Treiber liefert Objekte zurück, Row die denselben String-Key-Zugriff sowie Attribut- und Indexzugriff unterstützen:

SQLite (mit row_factory):

row["name"]

mssql-python:

row["name"]   # String-key access, like SQLite
row.name      # Attribute access
row[0]        # Index access

Parameterstil aktualisieren

Sowohl SQLite als auch mssql-python dienen ? als Parametermarker, sodass die meisten Abfragen ohne Änderungen funktionieren.

# Works with both sqlite3 and mssql-python
cursor.execute("SELECT * FROM Production.Product WHERE Name = ?", ("Adjustable Race",))

Der einzige Unterschied: SQLite erlaubt benannte Parameter mit Syntax :name . Der mssql-python-Treiber verwendet %(name)s stattdessen.

SQLite:

cursor.execute("SELECT * FROM products WHERE id = :id", {"id": 42})

mssql-python:

cursor.execute("SELECT * FROM Production.Product WHERE ProductID = %(id)s", {"id": 42})

Migrierung bestehender Daten

Wichtige Überlegungen bei der Migration von Daten von SQLite zu Microsoft SQL:

  • Microsoft SQL speichert nvarchar(max)- und varbinary(max)-Werte als große Objekte (LOBs), die langsamer zu lesen und zu schreiben sind als In-Row-Daten. Halte die String-Spalten auf nvarchar(4000) oder weniger, wann immer deine Daten es zulassen. SQL Server speichert diese Werte direkt in der Datenzeile und vermeidet so den LOB-Overhead.
  • Die Typabbildung folgt den Typaffinitätsregeln von SQLite. Überprüfen Sie die generierten Tabellen nach der Migration, um die Spaltengrößen zu verschärfen (zum Beispiel nvarchar(100) statt nvarchar(4000)) oder fügen Sie Einschränkungen hinzu, die SQLite nicht erzwungen hat.
  • Für SQLite-Tabellen, die TEXT zur Datenspeicherung verwenden, musst du die Werte möglicherweise vor dem Einfügen in Python-Objekte datetime umwandeln. Microsoft SQL erwartet korrekte Datetime-Werte, keine Textzeichen.

Verwenden Sie dieses Skript, um das Schema und die Daten aus SQLite auszulesen und entsprechende Tabellen in Microsoft SQL zu erstellen:

import sqlite3
import mssql_python

# SQLite type affinity -> Microsoft SQL type
# Keywords follow SQLite's type affinity rules
TYPE_MAP = {
    "INT": "bigint",
    "CHAR": "nvarchar(4000)",
    "CLOB": "nvarchar(max)",
    "TEXT": "nvarchar(4000)",
    "BLOB": "varbinary(max)",
    "REAL": "float",
    "FLOA": "float",
    "DOUB": "float",
}


def map_type(sqlite_type: str) -> str:
    """Map a SQLite column type to a Microsoft SQL type."""
    upper = (sqlite_type or "TEXT").upper()
    for prefix, sql_type in TYPE_MAP.items():
        if prefix in upper:
            return sql_type
    return "decimal(18,6)"  # NUMERIC affinity (default)


# Connect to both databases
sqlite_conn = sqlite3.connect("myapp.db")
sql_conn = mssql_python.connect(
    "Server=<server>.database.windows.net;"
    "Database=<database>;"
    "Authentication=ActiveDirectoryDefault;"
    "Encrypt=yes;"
)

# Get list of tables from SQLite
sqlite_cur = sqlite_conn.cursor()
sqlite_cur.execute(
    "SELECT name FROM sqlite_master "
    "WHERE type='table' AND name NOT LIKE 'sqlite_%'"
)
tables = [row[0] for row in sqlite_cur.fetchall()]

sql_cursor = sql_conn.cursor()

for table in tables:
    # Read column info from SQLite
    sqlite_cur.execute(f"PRAGMA table_info([{table}])")
    columns = sqlite_cur.fetchall()
    # columns: (cid, name, type, notnull, default_value, pk)

    # Build CREATE TABLE statement
    col_defs = []
    for col in columns:
        name, col_type, notnull, pk = col[1], col[2], col[3], col[5]
        sql_type = map_type(col_type)
        parts = [f"[{name}] {sql_type}"]
        if notnull:
            parts.append("NOT NULL")
        if pk:
            parts.append("PRIMARY KEY")
        col_defs.append(" ".join(parts))

    create_sql = f"CREATE TABLE [{table}] ({', '.join(col_defs)})"
    sql_cursor.execute(
        f"IF OBJECT_ID('{table}','U') IS NULL " + create_sql
    )
    sql_conn.commit()

    # Read all rows from SQLite
    sqlite_cur.execute(f"SELECT * FROM [{table}]")
    rows = sqlite_cur.fetchall()

    if not rows:
        print(f"  {table}: created (empty)")
        continue

    # Use bulkcopy for fast insert
    result = sql_cursor.bulkcopy(table, rows)
    print(f"  {table}: {result['rows_copied']} rows copied")

sql_conn.commit()
sqlite_conn.close()
sql_conn.close()

Unterschiede zwischen den Funktionen

Nach der Migration erhält Ihre Anwendung Zugriff auf Microsoft SQL-Funktionen, die SQLite nicht unterstützt:

Funktion SQLite SQL Server
Gleichzeitige Schreibvorgänge Einzelne Autorin nach der anderen Volle Nebenläufigkeit mit Sperrung auf Zeilenebene
Authentifizierung Nur Dateiberechtigungen SQL-Authentifizierung, Windows-Authentifizierung, Microsoft Entra ID
Gespeicherte Prozeduren Nicht unterstützt Vollständige T-SQL-Programmierbarkeit
Encryption Nicht eingebaut TLS unterwegs, TDE in Ruhe
Transaktionen Sicherungspunkte, grundlegende Isolationsstufen Vollständige Isolationsstufen, verteilte Transaktionen
JSON-Unterstützung json_extract() OPENJSON(), JSON_VALUE(), FOR JSON
Volltextsuche FTS5-Erweiterung Eingebaute Volltextindexierung
Max. Datenbankgröße ~281 TB (praktische Grenze liegt niedriger) 524 Petabyte
Verbindungspooling N/A (in Bearbeitung) Eingebaut mit mssql-python