PythonでSQL Serverに大量のデータを送り込むとき、executemany()のループで何分も待たされた経験はないでしょうか。

Microsoftが開発する公式Pythonドライバ「mssql-python」は、2025年11月のGA(正式リリース)から半年で6回のメジャーアップデートを重ね、Bulk Copy(一括挿入)とApache Arrow(ゼロコピーデータ取得)という2つの注目機能を搭載しました。

この記事でわかること

  • mssql-pythonの概要とpyodbcとの違い
  • Bulk Copyで大量行を高速に挿入する方法
  • Apache Arrowでpandas・Polarsへ効率的にデータを渡す方法
  • v1.6で改善されたマルチスレッド性能

mssql-pythonとは

mssql-pythonは、MicrosoftがSQL Server・Azure SQL Database・Microsoft Fabric向けに開発している公式Pythonドライバです。従来のPython×SQL Serverの定番はpyodbcでしたが、mssql-pythonはC++とpybind11で構築された独自アーキテクチャ(DDBC)を採用し、ODBCドライバの別途インストールなしで動作します。

pip install mssql-pythonの1コマンドで導入でき、DB API 2.0に準拠しているため、既存コードからの移行もスムーズです。Entra ID認証やコネクションプーリングも標準で備えています。2025年11月のv1.0リリースから2026年4月のv1.6まで、月1回以上のペースで機能追加が続いています。

Bulk Copyで数百万行を一括挿入する

https://techcommunity.microsoft.com/blog/sqlserver/mssql-python-1-4-bulk-copy-arrives—load-millions-of-rows-at-native-speed/4503171

v1.4で追加されたBulk Copy(BCP)は、SQL ServerのネイティブなTDS一括挿入プロトコルを使って大量のデータを送り込む機能です。通常のexecutemany()では1行ごとにSQLのパース・プランコンパイル・ラウンドトリップが発生しますが、Bulk Copyはこれらをすべて省略します。

使い方は3行で完結します。

import mssql_python

conn = mssql_python.connect(
    "SERVER=myserver;DATABASE=mydb;"
    "UID=user;PWD=pass;Encrypt=yes"
)
cursor = conn.cursor()

rows = [
    (1, "Alice", "alice@example.com"),
    (2, "Bob", "bob@example.com"),
]
result = cursor.bulkcopy("dbo.Users", rows)
print(f"{result['rows_copied']}行を"
      f"{result['elapsed_time']:.2f}秒で挿入")

バッチサイズ、テーブルロック、制約チェック、トリガー発火の制御など、.NETのSqlBulkCopyと同等のオプションも指定できます。内部ではRustで実装されたコアライブラリ(mssql-py-core)がTDSプロトコルを処理しており、Pythonのオーバーヘッドを最小限に抑えています。

Apache Arrowでゼロコピーのデータ取得

v1.5では、SQL Serverの結果セットをApache Arrow形式で直接取得するAPIが追加されました。従来のfetchall()はすべての値をPythonオブジェクトに変換するため、大量データではボトルネックになります。Arrow APIはC Data Interfaceを使い、C++レイヤーからPyArrowへゼロコピーで受け渡します。カラムナ形式のまま一貫して処理されるため、中間コピーが発生しません。

3つのメソッドが用途に応じて使い分けられます。

  • cursor.arrow() — 結果セット全体をPyArrow Tableとして取得し、pandasやPolarsへそのまま変換できる
  • cursor.arrow_batch(batch_size=10000) — 指定サイズのRecordBatchを1つずつ取得し、メモリを細かく制御したい場合に使う
  • cursor.arrow_reader(batch_size=8192) — ストリーミング用のRecordBatchReaderを返し、Parquetファイルへの直接書き出しに向いている
cursor.execute("SELECT * FROM Sales.SalesOrderDetail")
table = cursor.arrow()
df = table.to_pandas()  # ゼロコピーでpandasに変換

SQL Serverの各データ型(INT→int32、BIGINT→int64、DECIMAL→decimal128など)はすべて適切なArrow型にマッピングされます。この機能はコミュニティからのコントリビューションで実現しました。

v1.5のその他の新機能

Arrow対応と同時に、v1.5ではsql_variant型のサポートとUUIDのネイティブ返却が追加されています。

sql_variant型は、1つのカラムに異なるデータ型の値を格納するSQL Server独自の型です。設定テーブルやEAV(Entity-Attribute-Value)パターンで使われます。mssql-pythonはsql_variantの内部型タグを読み取り、Pythonの適切な型(int、float、str、datetime.dateなど)で自動的に返します。

UNIQUEIDENTIFIER列は、v1.5からデフォルトでPythonのuuid.UUIDオブジェクトとして返されるようになりました。pyodbcでは文字列で返されるため、手動変換が必要でした。既存コードとの互換性を保ちたい場合は、接続時にnative_uuid=Falseを指定すれば従来の文字列形式に戻せます。

v1.6でマルチスレッド性能が向上

2026年4月24日にリリースされたv1.6では、SQLDriverConnectSQLDisconnectの実行中にPythonのGIL(Global Interpreter Lock)を解放する改修が入りました。DNS解決、TCPハンドシェイク、TLSネゴシエーション、認証といったブロッキングI/O中に他のスレッドが止まらなくなります。10スレッドの同時接続テストでは、従来比約5.7倍の高速化が確認されています。

セキュリティ面では、setup_logging()のログファイルパスにディレクトリトラバーサル対策が追加されました。../を含む相対パスは拒否され、正規化されたパスのみが受け入れられます。

pyodbcから移行するには

mssql-pythonはDB API 2.0に準拠しているため、import文と接続文字列を変更するだけで動くケースが多いです。ODBCドライバの別途インストールが不要になる点は、DockerコンテナやCI/CD環境で特に恩恵が大きいでしょう。

注意すべき違いとして、前述のUUID型のデフォルト挙動があります。文字列を前提としたコードにはnative_uuid=Falseオプションで対応できます。Bulk CopyやArrow APIはpyodbcにない機能なので、移行後に段階的に導入するのが現実的です。

今後のロードマップ

公式ロードマップ(参考)では、asyncioによる非同期クエリ実行、SQL Server 2025のベクター型サポート、テーブル値パラメータ(TVP)対応が予定されています。半年で1.0から1.6まで到達した開発ペースを踏まえると、これらの機能追加も近い時期に期待できます。