はじめに
Go で MySQL を操作する処理を書いているときに、BULK INSERT したいと思うときがありました。
日本語だと、外部パッケージに頼らずに BULK INSERT するための情報があまりなかったので、記事で残そうと思います。
念の為の説明ですが、BULK INSERT とは、複数レコードを一回のINSERTで作成することを言います。
結論(コード)
早速ですがこちらが実装したコードです。
package main
import (
"database/sql"
"fmt"
"log"
_ "github.com/go-sql-driver/mysql"
)
var DB *sql.DB
// DBと接続
func init() {
// databaseはあらかじめ作成しておく
dsn := "root:root@(127.0.0.1:3306)/test?interpolateParams=true"
var err error
DB, err = sql.Open("mysql", dsn)
if err != nil {
log.Fatal("OpenError: ", err)
}
if err := DB.Ping(); err != nil {
log.Fatal("PingError: ", err)
}
}
func main() {
// tableを作成
_, err := DB.Exec(`create table if not exists names(id int, name varchar(20), primary key(id))`)
if err != nil {
log.Fatal("Exec to create table error: ", err)
}
// insert文の用意
s := []string{"hoge", "fuga", "piyo"}
insert := "INSERT INTO names(id, name) VALUES "
vals := make([]any, 0, len(s))
for i, k := range s {
insert += fmt.Sprintf(`(%v, ?),`, i)
vals = append(vals, k)
}
insert = insert[:len(insert)-1]
fmt.Println(insert)
// prepareして実行
stmt, err := DB.Prepare(insert)
if err != nil {
log.Fatal("Prepare error: ", err)
}
if _, err := stmt.Exec(vals...); err != nil {
log.Fatal("Exec error: ", err)
}
}
実行後のターミナル画面(作成したinsert文を標準出力)
% go run main.go
INSERT INTO names(id, name) VALUES (0, ?),(1, ?),(2, ?)
結果
mysql> select * from names;
+----+------+
| id | name |
+----+------+
| 0 | hoge |
| 1 | fuga |
| 2 | piyo |
+----+------+
3 rows in set (0.00 sec)
解説
sql.Open
や Prepare
、Exec
については解説しません。情報がたくさんあるので、他サイトや記事を参考にしていただきたいです。
今回見るのは、insert文を作成したこちらの処理になります。
s := []string{"hoge", "fuga", "piyo"}
insert := "INSERT INTO names(id, name) VALUES "
vals := make([]any, 0, len(s))
for i, k := range s {
insert += fmt.Sprintf(`(%v, ?),`, i)
vals = append(vals, k)
}
insert = insert[:len(insert)-1]
for文を使って、作成するレコードの数だけ (%v, ?),
をinsert文に追加しています。
for文が終わった後、末尾のカンマを取り除いています。
言わずもがなと思いますが、for文の文字列 k
を直接 insert文 に入れずに ?
を使うのはSQLインジェクション対策です。
また、any型で新しくスライスを作り直しているのは、stmt.Exec(vals...)
を使う時の引数に、string型のスライスは引数として使えないからです(今後、言語の仕様が変わって使えるようになるかもしれません)
vals
に入れる要素の数は明白なため、 make
で capacity を指定しています。
insert文と引数に入れる vals
ができれば、あとは普通にSQLの処理を書くだけです。
最後に
Go で BULK INSERT を行う方法を記述しました。
Go と MySQL 両方の処理速度を考えなければならないので、もっと良い書き方があるかもしれません。
どちらもまだ知識不足なので、今後アップデートしていければなと思います。