🤡
【Go】sqlcでbulk insertをする方法
はじめに
大量データを一括で挿入したい場面は多いのではないでしょうか?
例えばバッチ処理で数百万件のデータを扱うとき、通常のINSERT文を1件ずつ実行すると、ネットワーク往復やトランザクションオーバーヘッドで処理時間が膨大になります。
PostgreSQLが提供するCOPYプロトコルを利用したbulk insert(まとめて挿入)を使うことで、シーケンシャルなINSERTと比べて数十倍〜数百倍の高速化が可能です。
Go向けORMツールのsqlcでは、コメントに:copyfromを付与するだけでこのCOPYプロトコルを簡単に利用できます。[1]
結論
SQLファイルのコメントに:copyfromを付与するだけで、bulk insertが可能になります。
query.sql
-- name: BulkInsertUsers :copyfrom
INSERT INTO users (name, age) VALUES ($1, $2);
基本的なやり方
まずは最小構成で動かしてみましょう。
schema.sql
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
name TEXT NOT NULL,
age INT NOT NULL
);
query.sql
-- name: BulkInsertUsers :copyfrom
INSERT INTO users (name, age) VALUES ($1, $2);
sqlc.yaml
version: "2"
sql:
- engine: "postgresql"
schema: "schema.sql"
queries: "query.sql"
gen:
go:
package: "db"
sql_package: "pgx/v5"
out: "db"
トランザクションを使用したやり方
生成されたbulk insertメソッドは、トランザクション内でもそのまま利用できます。以下はpgxpool.PoolのBegin/Commitを使った例です。
main.go
func BulkInsertWithTx(ctx context.Context, pool *pgxpool.Pool, users []db.BulkInsertUsersParams) error {
tx, err := pool.Begin(ctx) // pgxpool.Begin でトランザクションを開始 :contentReference[oaicite:0]{index=0}
if err != nil {
return err
}
defer tx.Rollback(ctx)
q := db.New(tx)
count, err := q.BulkInsertUsers(ctx, users)
if err != nil {
return err
}
if err := tx.Commit(ctx); err != nil {
return err
}
fmt.Printf("Inserted %d rows\n", count)
return nil
}
トランザクションとの組み合わせ方法については、sqlc公式ドキュメント「Using transactions」を参照してください 。
copyfromのメリット・デメリット
-
メリット
- シーケンシャル
INSERTと比べて数十倍〜数百倍の高速化が可能。 - Goコード側はスライスを渡すだけでOK。ループや個別
Execを自前で書く必要がない。
- シーケンシャル
-
デメリット
-
COPY FROMは複雑なWHERE付き挿入や部分的挿入には対応しないため、全件一括投入に限定される。 - エラーが発生するとデフォルトで処理全体が中断される(
ON_ERROR stop)、細かいレコード単位の例外ハンドリングが難しい。
-
まとめ
本記事では、sqlcでbulk insertを行う方法を紹介しました。ポイントは以下の通りです。
- SQLファイルのコメントに
:copyfromを付与するだけでOK。 -
sql_packageにpgx/v5またはpgx/v4を指定。 - トランザクション内でもそのまま利用可能。
Discussion