使用 mssql-python 從 SQLite 遷移到 Microsoft SQL

SQLite 是許多 Python 專案、FastAPI 和 Flask 應用程式的預設資料庫。 它讓你能透過延遲資料平台的決策,快速建置應用程式。 當你的應用程式要投入生產環境時,你需要支援同時使用者、基於角色的安全性、高可用性、災難復原以及其他企業功能。 你需要使用 mssql-python 驅動程式遷移到 Microsoft SQL。

SQL 方言差異

當你從 SQLite 遷移到 Microsoft SQL 時,你需要處理兩件事:重寫 Transact-SQL 的 SQL 語句(T-SQL),以及遷移資料。

下表將常見的 SQLite 模式對應至 Microsoft SQL 對應模式:

SQLite SQL Server (T-SQL) Notes
INTEGER PRIMARY KEY AUTOINCREMENT int IDENTITY(1,1) PRIMARY KEY Microsoft SQL 會用IDENTITY來自動遞增。
TEXT nvarchar(255) 或 nvarchar(max) 一定要指定長度。 使用 nvarchar 表示 Unicode。
REAL float 或 decimal(18,2) 用 decimal 來計算像金錢這樣的精確價值。
BLOB varbinary(max) 行為一樣,名字不同。
BOOLEAN (以整數儲存) bit 這兩個資料庫都沒有原生布林值。 兩者都儲存 0/1。
DATETIME('now') GETDATE() 或 SYSDATETIME() SYSDATETIME() 提供更高的精度。
LIMIT 10 OFFSET 20 OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY 需要條款 ORDER BY 。
\|\|(字串串接) + 或 CONCAT() CONCAT() 處理 NULL 數值。
IFNULL(a, b) ISNULL(a, b) 或 COALESCE(a, b) COALESCE 是ANSI標準。
GROUP_CONCAT(col) STRING_AGG(col, ',') 可在 SQL Server 2017+ 中取得。
INSERT OR REPLACE INTO MERGE 陳述 SQLite 刪除與重新插入; MERGE 更新已到位。 請參考以下範例。
last_insert_rowid() OUTPUT INSERTED.id 在 OUTPUT 陳述式中使用 INSERT。 SCOPE_IDENTITY() 也可行,但需要另一個 SELECT。
typeof(x) SQL_VARIANT_PROPERTY(x, 'BaseType') 嚴格輸入時很少需要。

CREATE TABLE 範例

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

查詢範例

頁碼:

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

參數順序相反。 Transact-SQL 將 OFFSET 放在 FETCH NEXT 前面。

Upsert(插入或更新):

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

該 USING 子句定義 source.[key] 和 source.value 作為欄位別名。 這些 WHEN 條款就是指那些別名。 只需要兩個 ? 標記。

最後輸入的身分證:

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

更新連線代碼

替換 sqlite3.connect() 為 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;"
    )

列存取的運作方式也類似。 SQLite 的 Row 工廠會回傳類似 dict 的資料列,並可使用 row["column"] 語法存取。 mssql-python 驅動程式會傳回支援相同字串鍵存取的 Row 物件,並另外支援屬性與索引存取:

SQLite(含 row_factory):

row["name"]

MSSQL-python:

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

更新參數樣式

SQLite 和 mssql-python 都用作 ? 參數標記,因此大多數查詢都能在不做更改的情況下運作。

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

唯一的差別是:SQLite 允許有語法的 :name 命名參數。 mssql-python 驅動程式則使用 %(name)s 。

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

遷移現有資料

從 SQLite 遷移到 Microsoft SQL 時的重要考量:

  • Microsoft SQL 將 nvarchar(max)和 varbinary(max)值儲存為大型物件(LOB),這些物件的讀寫速度比列內資料慢。 只要資料允許,就把字串欄位設在 nvarchar(4000) 或更低。 SQL Server 將這些值直接儲存在資料列中,避免了 LOB 的開銷。
  • 型別映射遵循 SQLite 的 型別親和規則。 遷移後檢視產生的資料表,以調整欄位大小(例如 nvarchar(100) 取代 nvarchar(4000))或新增 SQLite 未強制執行的限制。
  • 對於使用 TEXT 來儲存日期的 SQLite 表格,你可能需要先將數值解析成 Python datetime 物件再插入。 Microsoft SQL 期望的是正確的 datetime 值,而不是文字字串。

使用此腳本讀取 SQLite 的結構與資料,並在 Microsoft SQL 中建立相符的資料表:

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

功能差異

遷移後,您的應用程式將獲得 Microsoft SQL 功能,而 SQLite 不支援:

Feature SQLite SQL Server
同時寫入 一次只寫一位作家 具備資料列層級鎖定的完整並行處理
Authentication 僅限檔案權限 SQL 驗證、Windows 驗證、Microsoft Entra ID
預存程序 不支援 完整的 T-SQL 可程式化性
Encryption 不是內建的 TLS在運送中,TDE靜止
Transactions 儲存點、基本隔離層級 完整隔離層級,分散式交易
JSON 支援 json_extract() OPENJSON()、JSON_VALUE()、FOR JSON
全文搜尋 FTS5 擴充 內建全文索引
資料庫大小上限 ~281 TB(實際限制較低) 524 PB
連線池化 不適用(處理中) 內建於 mssql-python