드라이버는 mssql-python Microsoft SQL에서 데이터를 읽기 위한 여러 경로를 제공합니다. 각 경로는 서로 다른 업무량에 맞습니다. 이 가이드는 데이터 크기, 분석 필요, 성능 요구사항에 맞춰 적합한 제품을 선택하는 데 도움을 줍니다.
업무량에 따라 결정하세요
이 표를 사용해 출발점을 찾으세요:
| 업무량 | 권장 경로 | 이유 |
|---|---|---|
| 애플리케이션 행 접근 (웹 API, CRUD) | 커서 가져오기 메서드 | 낮은 오버헤드, 한 번에 행 단위로 처리, 추가 의존성 없음. |
| 소규모에서 중간 규모 신고 쿼리 | pandas | 필터링, 그룹화, 시각화를 위한 익숙한 API입니다. |
| 대형 결과 집합 또는 넓은 표 | 화살표 추출 | 제로 카피 컬럼형 전송, 메모리 오버헤드 최소화. |
| 고성능 분석기 | 애로우와 함께하는 극지 | 열형 데이터에 대한 멀티스레드 실행, GIL 경쟁 없음. |
| 로컬 및 원격 데이터 위의 애드혹 SQL | Arrow와 함께하는 DuckDB | 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는 드라이버로부터 0 복사 데이터 전송을 제공하여 데이터프레임 fetchall()을 생성할 때 발생하는 행별 변환 오버헤드를 피합니다.
컬럼스토어 인덱스가 있는 테이블은 이미 데이터베이스 엔진에서 컬럼 형식으로 저장되어 있어, 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")
화살표 테이블은 판다, 폴라스, 덕DB의 출발점입니다. 한 번 추출한 후 변환하세요:
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"]))
더 큰 결과 집합의 경우 fetchall() 대신 Arrow에서 DataFrame을 생성하세요:
cursor.execute("SELECT * FROM Sales.SalesOrderHeader")
arrow_table = cursor.arrow()
df = arrow_table.to_pandas()
ETL, 시계열, 쓰기 등 완전한 판다스 패턴에 대해서는 판다스 통합을 참조하세요.
애로우와 함께하는 극지
더 큰 결과 세트에서 더 빠른 데이터프레임 작업이 필요할 때는 Polar를 사용하세요. 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)
폴라스 전체 패턴에 대해서는 폴라 적분을 참조하세요.
애로우와 함께하는 덕DB
추출된 데이터에 대해 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
""")
파르케로 내보내기:
cursor.execute("SELECT * FROM Sales.SalesOrderHeader")
orders = cursor.arrow()
duckdb.sql("COPY orders TO 'orders.parquet' (FORMAT PARQUET)")
DuckDB 전체 패턴은 DuckDB 통합을 참조하세요.
읽기 경로 결정에 영향을 미치는 Microsoft SQL 기능
데이터베이스 엔진은 어떤 읽기 경로가 가장 잘 작동하는지에 직접적인 영향을 주는 기능들을 가지고 있습니다. 접근법을 선택할 때 다음 특징들을 고려하세요:
컬럼스토어 인덱스
컬럼스토어 인덱스가 있는 테이블은 데이터를 컬럼 형식으로 저장합니다. 이 테이블들은 데이터가 엔진 내에서 이미 컬럼 형식으로 구성되어 있으므로 Arrow 추출에 자연스럽게 적합합니다. 분석 쿼리가 수백만 행의 넓은 테이블을 스캔한다면, 서버 측의 비클러스터 컬럼스토어 인덱스와 클라이언트 측의 Arrow 추출을 결합하면 최고의 엔드 투 엔드 처리량을 제공합니다.
인덱스가 적용된 뷰
인덱싱된 뷰는 집계되거나 결합된 결과를 미리 계산하고 서버에 저장합니다. 만약 pandas나 Polars 분석에서 동일한 집계가 반복적으로 계산된다면, 인덱스 뷰를 만들어 그 뷰를 쿼리하는 것을 고려해 보세요. 서버는 기본 데이터 변경 시 자동으로 뷰를 유지합니다.
쿼리 스토어 (쿼리 저장소)
쿼리 저장소는 시간에 따른 쿼리 실행 통계를 추적합니다. 어떤 쿼리가 Arrow 추출과 로컬 DataFrame 분석을 정당화할 만큼 비용이 큰지, 아니면 커서로 직접 읽는 편이 나은지 식별하는 데 사용하세요. 쿼리가 밀리초 단위로 실행된다면 커서 페치는 문제없습니다. 수백만 행을 스캔한다면, Arrow 추출과 로컬 분석을 통해 서버 부하를 줄일 수 있습니다.
지능형 쿼리 처리
Microsoft SQL의 지능형 쿼리 처리 기능인 적응형 조인, rowstore의 배치 모드, 메모리 부여 피드백 등은 쿼리 실행을 자동으로 최적화합니다. 이 기능들은 어떤 클라이언트 읽기 경로를 선택하든 상관없이 작동하지만, 큰 분석 쿼리에 가장 큰 이점을 제공합니다. 대부분의 작업량에 대해 힌트나 실행 계획을 조정할 필요가 없습니다.
대용량 결과 집합 스트리밍
메모리 내에 맞지 않는 결과 세트는 스트리밍 패턴을 사용하세요:
커서 기반 스트리밍: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
Parquet으로의 Arrow 기반 스트리밍:
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()
피해야 할 안티패턴
| 안티 패턴 | 문제 | 더 나은 접근 방식 |
|---|---|---|
fetchall() 그리고 pd.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. |