teratail header banner
teratail header banner
質問するログイン新規登録

回答編集履歴

4

文言の強調

2020/01/27 03:44

投稿

nobonobo
nobonobo

スコア3367

answer CHANGED
@@ -69,5 +69,6 @@
69
69
  その数にコネクション数Mを掛けたNxM個のPrepareステートメントを作成してしまう可能性があります。
70
70
  コネクション数やPrepareステートメント数の上限を調整するという考えは
71
71
  データベースへのアクセス規模に応じてどのみち必要なのですが
72
- わざわざこれらの問題を呼び寄せてしまうバルク操作はやはりお勧めしません。
72
+ わざわざこれらの問題を呼び寄せてしまうバルク操作は
73
+ 「 **Goが並列に強いからこそ、お勧めしません。** 」
73
74
  (LL言語実装の多くは並列にDBアクセスをしないのでこの問題は起きにくいです)

3

もうしこしメリット追記

2020/01/27 03:44

投稿

nobonobo
nobonobo

スコア3367

answer CHANGED
@@ -61,6 +61,7 @@
61
61
  - PrepareサポートなDBではかなり性能が向上する
62
62
  - ナイーブなエラーのハンドリング実装を追加しやすい
63
63
  - コネクションプールによる実装(Go標準)でPrepareステートメントの消費量が抑えられる
64
+ - 他の複雑なクエリにも同様の記述で対応できる(select&updateなど)
64
65
 
65
66
  例えばMySQLではPrepareした性能向上効果は大きいのですが、
66
67
  デフォルトで16K個のPrepareステートメントを作ろうとした時点でエラーになります。

2

注意事項追記

2020/01/27 03:27

投稿

nobonobo
nobonobo

スコア3367

answer CHANGED
@@ -51,4 +51,22 @@
51
51
  log.Fatal(err)
52
52
  }
53
53
  }
54
- ```
54
+ ```
55
+
56
+ ## この方法の良いところ
57
+
58
+ - SQLの調整がしやすい
59
+ - 既にある項目をフラットなスライスに並べ直すオーバーヘッドがない
60
+ - reflectを使わないので性能劣化が無い
61
+ - PrepareサポートなDBではかなり性能が向上する
62
+ - ナイーブなエラーのハンドリング実装を追加しやすい
63
+ - コネクションプールによる実装(Go標準)でPrepareステートメントの消費量が抑えられる
64
+
65
+ 例えばMySQLではPrepareした性能向上効果は大きいのですが、
66
+ デフォルトで16K個のPrepareステートメントを作ろうとした時点でエラーになります。
67
+ SQLビルダーで一つのことに対しN個のPrepareステートメントを作ると
68
+ その数にコネクション数Mを掛けたNxM個のPrepareステートメントを作成してしまう可能性があります。
69
+ コネクション数やPrepareステートメント数の上限を調整するという考えは
70
+ データベースへのアクセス規模に応じてどのみち必要なのですが
71
+ わざわざこれらの問題を呼び寄せてしまうバルク操作はやはりお勧めしません。
72
+ (LL言語実装の多くは並列にDBアクセスをしないのでこの問題は起きにくいです)

1

僕の場合の実装例を追記

2020/01/27 03:22

投稿

nobonobo
nobonobo

スコア3367

answer CHANGED
@@ -1,4 +1,54 @@
1
1
  いくらかバルクインサートの事例はありますが、あまりオススメできません。
2
2
  パラメータとプレースホルダとの帳尻合わせしつつ、SQLを動的に組み立てることになります。その記述はトリッキーであり、SQL記述ミスを誘発しやすいです。
3
3
 
4
- GoではPreparedしたひとつインサートだけのステートメントを繰り返し呼ぶ方が確実だし、この場合SQLのパースコストが下げられて性能も良いかもしれません。
4
+ GoではPreparedしたひとつインサートだけのステートメントを繰り返し呼ぶ方が確実だし、この場合SQLのパースコストが下げられて性能も良いかもしれません。
5
+
6
+ 僕が複数レコードインサートを実装する場合は以下のサンプルと同じ方法を採ります。
7
+ [https://golang.org/pkg/database/sql/#Tx.Prepare](https://golang.org/pkg/database/sql/#Tx.Prepare)
8
+ ```go
9
+ package main
10
+
11
+ import (
12
+ "context"
13
+ "database/sql"
14
+ "log"
15
+ )
16
+
17
+ var (
18
+ ctx context.Context
19
+ db *sql.DB
20
+ )
21
+
22
+ func main() {
23
+ projects := []struct {
24
+ mascot string
25
+ release int
26
+ }{
27
+ {"tux", 1991},
28
+ {"duke", 1996},
29
+ {"gopher", 2009},
30
+ {"moby dock", 2013},
31
+ }
32
+
33
+ tx, err := db.Begin()
34
+ if err != nil {
35
+ log.Fatal(err)
36
+ }
37
+ defer tx.Rollback() // The rollback will be ignored if the tx has been committed later in the function.
38
+
39
+ stmt, err := tx.Prepare("INSERT INTO projects(id, mascot, release, category) VALUES( ?, ?, ?, ? )")
40
+ if err != nil {
41
+ log.Fatal(err)
42
+ }
43
+ defer stmt.Close() // Prepared statements take up server resources and should be closed after use.
44
+
45
+ for id, project := range projects {
46
+ if _, err := stmt.Exec(id+1, project.mascot, project.release, "open source"); err != nil {
47
+ log.Fatal(err)
48
+ }
49
+ }
50
+ if err := tx.Commit(); err != nil {
51
+ log.Fatal(err)
52
+ }
53
+ }
54
+ ```