驅動mssql-python程式提供多條路徑來讀取 Microsoft SQL 的資料。 每條路徑適合不同的工作量。 本指南將根據您的資料量、分析需求及效能需求,幫助您選擇合適的方案。
依照工作量決定
請參考這張表格來尋找你的起點:
| [工作負載] | 建議的路徑 | 原因為何 |
|---|---|---|
| 應用程式列存取(Web API,CRUD) | 游標擷取方法 | 低開銷,一次只處理一列,沒有額外的依賴。 |
| 小型到中型報告查詢 | pandas | 熟悉的 API 用於篩選、分組與視覺化。 |
| 大量結果集或寬型資料表 | 箭頭擷取 | 零拷貝欄式傳輸,記憶體開銷極低。 |
| 高效能分析 | 極地與箭俠 | 在柱狀資料上以多執行緒執行,無 GIL 爭用。 |
| 本地與遠端資料上的臨時 SQL | DuckDB 與 Arrow | 對 Arrow 資料表進行 SQL 分析,並與本機的 CSV/Parquet 檔案聯結。 |
| 筆記本探索 | pandas 或 Polars with Arrow | 根據團隊熟悉度和資料量來選擇。 |
游標擷取方法
當你需要列導向存取且不需額外依賴時,使用標準游標方法。 此方法是用於每次處理一列、回傳 API 回應或提供應用邏輯的應用程式碼的正確選擇。
import mssql_python
conn = mssql_python.connect(
server="<server>.database.windows.net",
database="<database>",
authentication="ActiveDirectoryDefault",
encrypt="yes"
)
cursor = conn.cursor()
# fetchone(): Process rows one at a time
cursor.execute("SELECT ProductID, Name, ListPrice FROM Production.Product WHERE ListPrice > %(threshold)s", {"threshold": 100})
row = cursor.fetchone()
while row:
print(f"{row.Name}: ${row.ListPrice:.2f}")
row = cursor.fetchone()
# fetchmany(): Process in batches
cursor.execute("SELECT ProductID, Name FROM Production.Product")
while True:
batch = cursor.fetchmany(100)
if not batch:
break
for row in batch:
print(row.Name)
# fetchval(): Get a single scalar value
cursor.execute("SELECT COUNT(*) FROM Production.Product")
count = cursor.fetchval()
使用 fetchmany() 來對大型結果集進行節省記憶體的批次處理。 當你需要單一數值,比如計數、最大值或存在檢定時使用 fetchval() 。
完整的擷取方法文件,請參見 「擷取資料」。
箭頭擷取
當你需要欄位資料用於分析、建構 DataFrame 或匯出到 Parquet 時,請使用 Arrow 擷取。 Arrow 可從驅動程式進行零複製資料傳輸,避免從 fetchall() 建立 DataFrame 時逐列轉換的開銷。
帶有欄位儲存索引的資料表已以欄位格式儲存在資料庫引擎中,使 Arrow 擷取對這些工作負載來說非常自然。
cursor.execute("""
SELECT ProductID, Name, ListPrice, Color
FROM Production.Product
WHERE ListPrice > 0
""")
# Get a single Arrow table
arrow_table = cursor.arrow()
print(f"{arrow_table.num_rows} rows, {arrow_table.num_columns} columns")
對於大型結果集,可使用 arrow_reader() 以串流方式處理批次,而不必將所有資料載入記憶體中:
cursor.execute("SELECT * FROM Sales.SalesOrderHeader")
# Stream Arrow record batches
reader = cursor.arrow_reader(batch_size=10000)
for batch in reader:
# Each batch is a pyarrow.RecordBatch
print(f"Batch: {batch.num_rows} rows")
Arrow 資料表是 pandas、Polars 和 DuckDB 的基礎。 擷取一次,然後轉換:
cursor.execute("""
SELECT ProductID, Name, ListPrice, Color
FROM Production.Product
WHERE ListPrice > 0
""")
arrow_table = cursor.arrow()
# Arrow -> pandas
df = arrow_table.to_pandas()
# Arrow -> Polars (zero-copy)
import polars as pl
df = pl.from_arrow(arrow_table)
完整 Arrow 文件請參閱 Apache Arrow 整合。
pandas
當你需要熟悉的 DataFrame API 來做報告、臨時分析或資料清理時,請使用 pandas。 pandas 最適合處理可放入記憶體的結果集(最多可達數百萬列,視欄寬而定)。
cursor.execute("""
SELECT p.Name, p.ListPrice, pc.Name AS Category
FROM Production.Product p
JOIN Production.ProductSubcategory ps ON p.ProductSubcategoryID = ps.ProductSubcategoryID
JOIN Production.ProductCategory pc ON ps.ProductCategoryID = pc.ProductCategoryID
WHERE p.ListPrice > 0
""")
import pandas as pd
rows = cursor.fetchall()
columns = [desc[0] for desc in cursor.description]
df = pd.DataFrame.from_records(rows, columns=columns)
# Analyze
print(df.groupby("Category")["ListPrice"].agg(["mean", "count"]))
若要取得較大的結果集,請使用 Arrow 建立 DataFrame,而非 fetchall():
cursor.execute("SELECT * FROM Sales.SalesOrderHeader")
arrow_table = cursor.arrow()
df = arrow_table.to_pandas()
如需 pandas 的完整模式,包括 ETL、時間序列和回寫,請參閱 pandas integration。
Polars 與 Arrow
當你需要更快的 DataFrame 運算處理較大的結果集時,可以使用 Polars。 Polars 使用 Apache Arrow 作為其記憶體格式,因此從 cursor.arrow() 傳輸資料時可做到零拷貝。 Polars 也能在多個執行緒上執行運算,從而避免 CPU 密集型轉換時的 GIL 爭用。
import polars as pl
cursor.execute("""
SELECT ProductID, Name, ListPrice, Color
FROM Production.Product
WHERE ListPrice > 0
""")
arrow_table = cursor.arrow()
df = pl.from_arrow(arrow_table)
# Filter and aggregate
result = (
df.filter(pl.col("ListPrice") > 100)
.group_by("Color")
.agg(pl.col("ListPrice").mean().alias("AvgPrice"))
.sort("AvgPrice", descending=True)
)
print(result)
如要串流大型結果集:
cursor.execute("SELECT * FROM Sales.SalesOrderHeader")
reader = cursor.arrow_reader(batch_size=50000)
frames = []
for batch in reader:
frames.append(pl.from_arrow(batch))
df = pl.concat(frames)
如需完整的 Polars 範例,請參閱 Polars 整合。
DuckDB 與 Arrow
當你需要對擷取資料執行 SQL 分析、將伺服器資料與本地 CSV 或 Parquet 檔案連結,或匯出結果成檔案格式時,使用 DuckDB。 DuckDB 在 Arrow 資料表上運作,採零複製存取權。
import duckdb
cursor.execute("""
SELECT ProductID, Name, ListPrice, Color
FROM Production.Product
WHERE ListPrice > 0
""")
products = cursor.arrow()
# Run DuckDB SQL on the Arrow table
result = duckdb.sql("""
SELECT Color, AVG(ListPrice) AS AvgPrice, COUNT(*) AS Count
FROM products
WHERE Color IS NOT NULL
GROUP BY Color
ORDER BY AvgPrice DESC
""")
print(result.fetchdf())
將伺服器資料與本地檔案連接:
cursor.execute("SELECT CustomerID, TerritoryID FROM Sales.Customer")
customers = cursor.arrow()
# Join with a local CSV file
result = duckdb.sql("""
SELECT c.CustomerID, c.TerritoryID, l.Region
FROM customers c
JOIN read_csv_auto('regions.csv') l ON c.TerritoryID = l.TerritoryID
""")
出口至Parquet:
cursor.execute("SELECT * FROM Sales.SalesOrderHeader")
orders = cursor.arrow()
duckdb.sql("COPY orders TO 'orders.parquet' (FORMAT PARQUET)")
完整 DuckDB 模式請參見 DuckDB 整合。
Microsoft SQL 功能影響讀路徑決策
資料庫引擎有直接影響哪條讀取路徑最適合的功能。 在選擇方案時請考慮以下特點:
欄存儲索引
具有 欄位儲存索引 的資料表以欄位格式儲存資料。 箭頭擷取是這些資料表的自然交接方式,因為資料在引擎中已經是欄位式的。 如果你的分析查詢掃描的是擁有數百萬列的寬資料表,伺服器端的非叢集欄位儲存索引結合客戶端的 Arrow 擷取,能提供最佳的端對端吞吐量。
具索引的檢視
索引檢視 會預先計算並儲存在伺服器上的聚合或合併結果。 如果你的 pandas 或 Polars 分析反覆重複計算相同的彙總結果,請考慮建立已建立索引的檢視,並改為查詢該檢視。 伺服器會自動維護該視圖,以反映底層資料的變動。
查詢儲存庫
查詢存放區 追蹤查詢執行統計數據隨時間變化。 用它來判斷哪些查詢的代價高到足以採用 Arrow 匯出並在本機上分析 DataFrame,而哪些則適合直接透過游標讀取。 如果查詢執行時間只有幾毫秒,使用游標擷取就沒問題。 如果掃描數百萬筆資料列,Arrow 擷取與本地分析可能會降低伺服器負載。
智慧查詢處理
Microsoft SQL 的智慧查詢處理功能,如自適應連接、列商店的批次模式及記憶體授權回饋,能自動優化查詢執行。 這些功能無論你選擇哪個客戶端閱讀路徑都能運作,但它們對大型分析查詢最為有利。 對於大多數工作負載,您不需要調整提示或執行計畫。
串流大型結果集
對於無法放入記憶體的結果集,請使用串流模式:
基於游標的串流方式:fetchmany()
cursor.execute("SELECT * FROM Sales.SalesOrderHeader")
while True:
batch = cursor.fetchmany(5000)
if not batch:
break
for row in batch:
print(row[0]) # Process each row
基於 Arrow 的串流 到 Parquet:
import pyarrow.parquet as pq
cursor.execute("SELECT * FROM Sales.SalesOrderHeader")
reader = cursor.arrow_reader(batch_size=50000)
writer = None
for batch in reader:
if writer is None:
writer = pq.ParquetWriter("orders.parquet", batch.schema)
writer.write_batch(batch)
if writer:
writer.close()
應避免的反模式
| 反模式 | Problem | 更好的方法 |
|---|---|---|
fetchall() 接著 pd.DataFrame() 用於大型表格 |
將所有資料列載入記憶體兩次(一次以元組形式,一次以 DataFrame 形式)。 | 使用 cursor.arrow(),然後使用 arrow_table.to_pandas()。 |
| 將 Arrow 轉成 pandas,只為了篩選列數 | 會在完整的 pandas 副本上浪費記憶體。 | 用 SQL(WHERE 子句)篩選,或直接在 Arrow 資料表上使用 Polars/DuckDB。 |
SELECT * 當你需要三欄時 |
從伺服器轉移不必要的資料。 | 只列出你需要的欄位。 |
建立一個用於計算的資料框架 COUNT(*) |
伺服器運算彙整的速度比 Python 快。 | 使用 SELECT COUNT(*) 和 fetchval()。 |
| 每次查詢開啟新連線 | 即使有連線池的額外負擔,建立連線的成本仍然很高。 | 在邏輯工作單元內重複使用連結。 |
| 鏈箭 -> 熊貓 -> 極地 | 每次轉換都會複製資料。 | 直接轉換為你的目標格式:Arrow -> Polars 或 Arrow -> pandas。 |