Bulk INSERTをトランザクションでラップする

BEGIN/COMMITトランザクションブロックでラップされたBulk INSERT文を生成し、原子性とパフォーマンスの向上を実現します。

Advanced

詳細な説明

トランザクションでラップされたBulkインサート

Bulk INSERT文をトランザクションでラップすると2つのメリットがあります:原子性(全成功か全失敗か)とパフォーマンス(コミットオーバーヘッドの削減)。

基本的なトランザクションラッパー

BEGIN;

INSERT INTO "events" ("id", "type", "timestamp")
VALUES
  (1, 'login', '2024-01-01 09:00:00'),
  (2, 'click', '2024-01-01 09:01:00'),
  -- ... さらに行 ...
  (100, 'logout', '2024-01-01 10:00:00');

INSERT INTO "events" ("id", "type", "timestamp")
VALUES
  (101, 'login', '2024-01-01 11:00:00'),
  -- ... 次のバッチ ...
  (200, 'purchase', '2024-01-01 12:00:00');

COMMIT;

SQL Serverバリアント

SQL ServerはBEGINの代わりにBEGIN TRANSACTIONを使用します:

BEGIN TRANSACTION;

INSERT INTO [events] ([id], [type], [timestamp])
VALUES
  (1, 'login', '2024-01-01 09:00:00');

COMMIT;

トランザクションが重要な理由

トランザクションなし: 各INSERT文が個別に自動コミットされます。

  • バッチ10のうちバッチ5が失敗した場合、バッチ1-4は既にコミット済み
  • 各自動コミットがディスク同期をトリガー(HDDでは低速)

トランザクションあり: すべてのバッチが成功するか、すべてがロールバックされます。

  • 原子的:部分的なインポートは不可能
  • 高速:N回の同期ではなくCOMMIT時に1回のディスク同期

パフォーマンスへの影響

シナリオ SQLite(1000行) PostgreSQL(1000行)
トランザクションなし 約2秒 約500ms
トランザクションあり 約50ms 約100ms

速度向上はSQLiteで最も劇的で、各自動コミットがファイルシステム同期を強制します。

ベストプラクティス

  1. トランザクションは短く保つ(10,000行未満)— ロック競合を回避
  2. スクリプトにエラーハンドリングを追加:BEGIN; INSERT ...; INSERT ...; COMMIT;で失敗時にロールバック
  3. 非常に大規模なインポートでは、5,000-10,000行ごとに複数のトランザクションに分割

ユースケース

テスト環境用のデータベースシードスクリプトを作成している場合。すべてのINSERTを単一のトランザクションでラップすることで、すべてのテストデータがロードされるか、まったくロードされないかのいずれかを保証し、部分的にシードされたデータベースによるテスト失敗を防ぎます。

試してみる — JSON to Bulk INSERT

フルツールを開く