Gebruik mssql-python met Apache Arrow

De mssql-python-driver biedt Apache Arrow-ophaalmethoden voor hoogpresterende kolomgegevensopvraging uit Microsoft SQL en Azure SQL Database.

Apache Arrow is een platform voor taaloverstijgende ontwikkeling voor kolomgegevens in het geheugen. De driver zet ODBC-resultaatsets direct om in het Arrow-formaat in C++, waarbij Python-objectcreatie wordt omzeild voor betere prestaties.

Arrow-integratie maakt het volgende mogelijk:

  • Zero-copy gegevensoverdracht naar Polars, pandas en DuckDB. "Zero-copy" betekent dat de data in één enkele geheugenbuffer blijft die de driver schrijft en die de consumerende bibliotheken direct lezen, zodat er geen rijen worden gedupliceerd in intermediaire Python-objecten.
  • Resultaatsets streamen via RecordBatchReader zonder alles in het geheugen te laden.
  • Columnar dataformaat ideaal voor analytics- en machine learning-workloads.
  • Minder geheugengebruik vergeleken met het opmaken van Python-objecten per rij.

Cursormethoden

Het pyarrow pakket moet Arrow-ophaalmethoden gebruiken. Installeer het met pip install pyarrow. Als pyarrow niet geïnstalleerd is, genereert het aanroepen van een Arrow-methode een ImportError.

De mssql-python-driver voegt drie methoden toe aan het cursorobject voor Arrow-data-toegang. Alle drie de methoden converteren ODBC-resultatensets naar Arrow-formaat in de C++-laag van de driver, waardoor het creëren van tussenliggende Python-objecten voorkomt.

  • arrow() geeft de volledige resultaatset terug als één in-memory tabel. Zijn het eenvoudigst te gebruiken.
  • arrow_batch() geeft één batch rijen tegelijk terug, waardoor je handmatige controle over de lus hebt.
  • arrow_reader() geeft een iterator terug die automatisch batches oplevert. Het beste voor het streamen van grote resultaten.

Het gebruiken van cursor.arrow(batch_size=8192)

Haal de volledige resultaatset op als één enkele pyarrow.Table. Deze methode is de eenvoudigste en werkt goed wanneer de volledige resultaatset in het geheugen past.

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

Opmerking

Als je verbindingsreeks Authentication=ActiveDirectoryDefault gebruikt, gebruikt de driver DefaultAzureCredential, die meerdere referentieproviders opeenvolgend probeert. De eerste verbinding kan traag zijn omdat de SDK de keten doorloopt totdat hij een werkende provider vindt. In productie, als je weet welk type inloggegevens je omgeving gebruikt, specificeer het dan direct (bijvoorbeeld ActiveDirectoryMSI voor managed identity) om de chain walk te voorkomen. Zie Microsoft Entra-verificatie voor meer informatie.

Het gebruiken van cursor.arrow_batch(batch_size=8192)

Haal één pyarrow.RecordBatch op met maximaal batch_size rijen. Gebruik deze methode voor aangepaste batchverwerkingslussen waarbij je fijnmazige controle nodig hebt over hoeveel rijen tegelijk worden opgehaald.

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

Het gebruiken van cursor.arrow_reader(batch_size=8192)

Geef een pyarrow.RecordBatchReader terug die objecten oplevert RecordBatch totdat de resultaatset is uitgeput. Deze methode is de meest geheugen-efficiënte optie voor grote resultaatsets.

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

Algemene patronen

Arrow-tabellen integreren direct met populaire Python-databibliotheken. De volgende voorbeelden laten zien hoe je Arrow-gegevens kunt doorgeven aan pandas, Polars, DuckDB en bestandsformaten zonder data te kopiëren.

Laad resultaten in 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())

Resultaten laden in Polars

import polars as pl

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

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

Zoekresultaten met DuckDB

DuckDB kan Arrow-tabellen direct in SQL bevragen zonder data te kopiëren. Deze mogelijkheid is handig wanneer je SQL-achtige analyses nodig hebt op resultaatsets die al in Arrow-formaat zijn.

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

Grote resultaatsets naar Parquet streamen

Voor grote resultaatsets streamt u Arrow-batches direct naar een Parquet-bestand zonder de volledige dataset in het geheugen te laden. De ParquetWriter schrijft elke batch incrementeel.

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

Export naar andere formaten

PyArrow biedt ingebouwde schrijvers voor CSV en het Arrow IPC-bestandsformaat (ook bekend als Feather V2). Arrow IPC-bestanden behouden Arrow-types exact en zijn snel terug te lezen.

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)

Gegevenstypetoewijzingen

De Arrow-ophaalmethoden mappen Microsoft SQL-types toe aan Arrow-types op C++-niveau.

Microsoft SQL-type Pijltype
int, smallint, tinyint, bigint int32,int16,int8,int64
Float, Reëel float64, float32
decimaal, numeriek decimal128
bit bool
Char, Varchar, Nchar, Nvarchar utf8
Tekst, Ntext large_utf8
binair, varbinair binary, large_binary
date date32
time time64[us]
Datetime, Datetime2, Smalldatetime timestamp[us]
datetimeoffset timestamp[us, tz=UTC]
uniqueidentifier utf8 (tekst in hoofdletters)
xml utf8

Opmerking

De chauffeur zet het datetimeoffset type om naar UTC omdat pijlkolommen een vaste tijdzone vereisen. De driver normaliseert de tijdzone-informatie per cel van Microsoft SQL naar UTC tijdens de conversie.

Het sql_variant type wordt niet ondersteund door Arrow-ophaalmethoden en veroorzaakt een uitzondering voor het niet-ondersteunde datatype. Gebruik standaard fetchone(), fetchmany() of fetchall() voor query's die sql_variant kolommen retourneren.

Prestatie-overwegingen

Arrow fetch-methoden zijn het snelst voor analytics en bulkdata-operaties, terwijl standaard cursormethoden beter geschikt zijn voor transactionele patronen met kleine resultatensets.

Wanneer te gebruiken met Arrow versus standaard fetch

Scenario Aanbevolen aanpak
Haal een paar rijen op voor weergave fetchone() / fetchall()
Laad data in pandas of Polars cursor.arrow()
Verwerk grote datasets in delen cursor.arrow_reader()
Zoekopdrachten met één rij of kleine resultaatsets fetchone() / fetchval()
Analytics- of aggregatiepijplijnen cursor.arrow() + Polars/DuckDB
Schrijf resultaten naar Parquet of Arrow IPC cursor.arrow_reader() + PyArrow I/O

Geheugenbeheer voor grote datasets

Voor resultaatsets die mogelijk het beschikbare geheugen overschrijden, gebruik arrow_reader() met een redelijke batch_size.

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

Batchgrootte afstemmen

De batch_size parameter bepaalt hoeveel rijen er per batch worden opgehaald. De optimale grootte hangt af van je rijbreedte en beschikbare geheugen. Bredere rijen met grote kolommen zoals nvarchar(max) of varbinary(max) profiteren van kleinere batchgroottes, terwijl smalle rijen profiteren van grotere.

  • Standaard (8192): Goede balans voor de meeste werklasten.
  • Kleiner (1000-5000): Gebruik voor brede tabellen met grote kolommen.
  • Groter (50000-100000): Gebruik voor smalle tabellen of wanneer doorvoer belangrijker is dan geheugen.
# 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)