使用 mssql-python 搭配 Apache Arrow

mssql-python 驅動程式提供 Apache Arrow 擷取方法,用於從 Microsoft SQL 和 Azure SQL Database 進行高效能欄位資料檢索。

Apache Arrow 是一個跨語言的記憶體欄位資料開發平台。 驅動程式直接將 ODBC 結果集轉換為 C++ 的 Arrow 格式,跳過 Python 物件建立以提升效能。

Arrow 整合可實現:

  • 零複製資料傳輸 至 Polars、Pandas 和 DuckDB。 「零複製」意指資料會停留在單一的記憶體緩衝區,驅動程式會寫入並直接讀取函式庫,因此不會有列重複成中間的 Python 物件。
  • 串流結果會直接通過 RecordBatchReader ,但不會把所有資料載入記憶體。
  • 欄位資料格式,非常適合分析與機器學習工作負載。
  • 相較於逐列 Python 物件建立,記憶體使用量更低。

游標方法

若要使用 Arrow 的 fetch 方法,則需要 pyarrow 套件。 使用 pip install pyarrow 安裝。 如果 pyarrow 沒有安裝,呼叫任何 Arrow 方法都會產生一個 ImportError

mssql-python 驅動程式在游標物件中新增三種 Arrow 資料存取的方法。 這三種方法都會將 ODBC 結果集轉換為驅動程式的 C++ 層中的 Arrow 格式,避免建立中間的 Python 物件。

  • arrow() 將整個結果集以一個記憶體內資料表回傳。 最簡單好用。
  • arrow_batch() 一次回傳一列,讓你能手動控制迴圈。
  • arrow_reader() 回傳一個自動產生批次的迭代器。 最適合用於串流大量結果。

使用 cursor.arrow(batch_size=8192)

將整個結果集擷取為單一的 pyarrow.Table。 此方法最簡單,且當完整結果集能放入記憶體時運作良好。

import mssql_python

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

cursor.execute("SELECT ProductID, Name, ListPrice FROM Production.Product")
table = cursor.arrow()

print(type(table))       # <class 'pyarrow.lib.Table'>
print(table.num_rows)    # Number of rows fetched
print(table.num_columns) # Number of columns
print(table.schema)      # Column names and Arrow types
print(table.to_pandas()) # Convert to pandas DataFrame

Note

如果你的 連接字串 使用Authentication=ActiveDirectoryDefault,驅動程式會使用 DefaultAzureCredential,並依序嘗試多個憑證提供者。 第一次連線可能會比較慢,因為 SDK 會一直走鏈條直到找到可用的供應商。 在生產環境中,如果您知道您的環境使用哪一種憑證類型,請直接指定該憑證類型(例如,針對受控識別可指定 ActiveDirectoryMSI),以避免逐一嘗試整個憑證鏈。 如需詳細資訊,請參閱 Microsoft Entra 驗證

使用 cursor.arrow_batch(batch_size=8192)

擷取單一pyarrow.RecordBatch,其中最多包含batch_size列。 這種方法用於需要細緻控制一次擷取幾列的自訂批次處理迴圈。

cursor.execute("SELECT * FROM Production.TransactionHistory")

while True:
    batch = cursor.arrow_batch(batch_size=10000)
    if batch.num_rows == 0:
        break
    # Process each batch
    print(f"Fetched {batch.num_rows} rows")

使用 cursor.arrow_reader(batch_size=8192)

回傳一個 pyarrow.RecordBatchReader,該物件會產生 RecordBatch 物件,直到結果集耗盡為止。 此方法是處理大型結果集時記憶體效率最高的選擇。

cursor.execute("SELECT * FROM Production.TransactionHistory")
reader = cursor.arrow_reader(batch_size=50000)

for batch in reader:
    # Process streaming batches without loading all data
    print(f"Batch: {batch.num_rows} rows")

常見模式

箭頭表格可直接整合於熱門的 Python 資料庫。 以下範例說明如何將 Arrow 資料傳給 pandas、Polars、DuckDB 及檔案格式,且不複製資料。

將結果載入 pandas 中

cursor.execute("SELECT * FROM Production.Product")
table = cursor.arrow()

# Convert to pandas with zero-copy where possible
df = table.to_pandas()
print(df.head())

將結果載入 Polars

import polars as pl

cursor.execute("SELECT * FROM Production.Product")
table = cursor.arrow()

df = pl.from_arrow(table)
print(df)

DuckDB 查詢結果

DuckDB 可以直接在 SQL 中查詢 Arrow 資料表,無需複製資料。 當你需要對已為 Arrow 格式的結果集進行 SQL 式分析時,這項功能非常有用。

import duckdb

cursor.execute("SELECT * FROM Sales.SalesOrderHeader")
arrow_table = cursor.arrow()

# Query the Arrow table with DuckDB SQL
result = duckdb.sql("SELECT CustomerID, SUM(TotalDue) FROM arrow_table GROUP BY CustomerID")
print(result.fetchall())

將大型結果集串流至 Parquet

對於大型結果集,可以直接將 Arrow 批次串流到 Parquet 檔案,無需將整個資料集載入記憶體。 ParquetWriter 會逐步寫入每個批次。

import pyarrow.parquet as pq

cursor.execute("SELECT * FROM Production.TransactionHistory")
reader = cursor.arrow_reader(batch_size=100000)

# Write streaming batches to a Parquet file
writer = None
for batch in reader:
    if writer is None:
        writer = pq.ParquetWriter("output.parquet", batch.schema)
    writer.write_batch(batch)

if writer:
    writer.close()

匯出至其他格式

PyArrow 內建 CSV 及 Arrow IPC 檔案格式(亦稱為 Feather V2)的寫入程式。 Arrow IPC 檔案能完整保留 Arrow 類型,且回讀速度快。

import pyarrow as pa
import pyarrow.csv as pcsv

cursor.execute("SELECT * FROM Production.Product")
table = cursor.arrow()

# Write to CSV
pcsv.write_csv(table, "products.csv")

# Write to an Arrow IPC file
with pa.ipc.new_file("products.arrow", table.schema) as writer:
    writer.write_table(table)

數據類型對應

Arrow 的擷取方法將 Microsoft SQL 類型映射到 C++ 層級的 Arrow 類型。

Microsoft SQL 類型 箭頭類型
intsmallinttinyintbigint int32int16int8int64
floatreal float64float32
十進位數字 decimal128
位元 bool
查爾瓦爾查爾恩查爾恩瓦爾查爾 utf8
文字ntext large_utf8
二進位可變長度二進位 binarylarge_binary
date date32
time time64[us]
約會時間約會時間2小約會時間 timestamp[us]
datetimeoffset timestamp[us, tz=UTC]
uniqueidentifier utf8 (大寫字串)
xml utf8

Note

驅動程式會 datetimeoffset 將類型轉換成 UTC,因為箭頭欄位需要固定的時區。 驅動程式在轉換時將每個單元時區資訊從 Microsoft SQL 標準化為 UTC。

Arrow 擷取方法不支援此 sql_variant 型別,且會觸發不支援的資料型別例外。 對於會傳回 sql_variant 個資料欄的查詢,請使用標準 fetchone()fetchmany()fetchall()

效能考量

箭頭擷取方法在分析與大量資料操作中速度最快,而標準游標方法則更適合處理具有小型結果集的交易模式。

什麼時候該用箭頭 還是標準抓球

Scenario 建議方法
擷取幾列資料供顯示 fetchone() / fetchall()
將資料載入 Pandas 或 Polar cursor.arrow()
將大型資料集分段處理 cursor.arrow_reader()
單列查詢或小型結果集 fetchone() / fetchval()
分析或彙整流程 cursor.arrow() + Polars/DuckDB
將結果寫入Parquet或Arrow IPC cursor.arrow_reader() + PyArrow I/O

大型資料集的記憶體管理

若結果集可能超出可用記憶體,請搭配合理的 batch_size 使用 arrow_reader()

cursor.execute("SELECT * FROM Production.TransactionHistory")

# Process in batches of 100K rows
reader = cursor.arrow_reader(batch_size=100000)
total_rows = 0

for batch in reader:
    # Work with each batch individually
    total_rows += batch.num_rows
    # batch goes out of scope and memory is freed

print(f"Processed {total_rows} rows")

調整批次大小

參數 batch_size 控制每批次擷取的列數。 最佳大小取決於你的列寬度和可用記憶體。 包含 nvarchar(max)varbinary(max) 等大型資料行的寬資料列較適合較小的批次大小,而窄資料列則較適合較大的批次大小。

  • 預設(8192):大多數工作負載的平衡良好。
  • 較小(1000-5000):用於欄位較寬的寬表格。
  • 較大(50000-100000):用於窄表或吞吐量比記憶體更重要時。
# Narrow table with many rows - use larger batches
cursor.execute("SELECT ProductID, ListPrice FROM Production.Product")
table = cursor.arrow(batch_size=100000)

# Wide table with LOB columns - use smaller batches
cursor.execute("SELECT * FROM Production.Document")
table = cursor.arrow(batch_size=1000)