2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Go + MySQL で BULK INSERTしてみた

Last updated at Posted at 2022-04-20

はじめに

Go で MySQL を操作する処理を書いているときに、BULK INSERT したいと思うときがありました。
日本語だと、外部パッケージに頼らずに BULK INSERT するための情報があまりなかったので、記事で残そうと思います。

念の為の説明ですが、BULK INSERT とは、複数レコードを一回のINSERTで作成することを言います。

結論(コード)

早速ですがこちらが実装したコードです。

main.go
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.OpenPrepareExec については解説しません。情報がたくさんあるので、他サイトや記事を参考にしていただきたいです。

今回見るのは、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 両方の処理速度を考えなければならないので、もっと良い書き方があるかもしれません。
どちらもまだ知識不足なので、今後アップデートしていければなと思います。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?