回答編集履歴

4

文言の強調

2020/01/27 03:44

投稿

nobonobo
nobonobo

スコア3367

test CHANGED
@@ -140,6 +140,8 @@
140
140
 
141
141
  データベースへのアクセス規模に応じてどのみち必要なのですが
142
142
 
143
- わざわざこれらの問題を呼び寄せてしまうバルク操作はやはりお勧めしません。
143
+ わざわざこれらの問題を呼び寄せてしまうバルク操作は
144
+
145
+ 「 **Goが並列に強いからこそ、お勧めしません。** 」
144
146
 
145
147
  (LL言語実装の多くは並列にDBアクセスをしないのでこの問題は起きにくいです)

3

もうしこしメリット追記

2020/01/27 03:44

投稿

nobonobo
nobonobo

スコア3367

test CHANGED
@@ -124,6 +124,8 @@
124
124
 
125
125
  - コネクションプールによる実装(Go標準)でPrepareステートメントの消費量が抑えられる
126
126
 
127
+ - 他の複雑なクエリにも同様の記述で対応できる(select&updateなど)
128
+
127
129
 
128
130
 
129
131
  例えばMySQLではPrepareした性能向上効果は大きいのですが、

2

注意事項追記

2020/01/27 03:27

投稿

nobonobo
nobonobo

スコア3367

test CHANGED
@@ -105,3 +105,39 @@
105
105
  }
106
106
 
107
107
  ```
108
+
109
+
110
+
111
+ ## この方法の良いところ
112
+
113
+
114
+
115
+ - SQLの調整がしやすい
116
+
117
+ - 既にある項目をフラットなスライスに並べ直すオーバーヘッドがない
118
+
119
+ - reflectを使わないので性能劣化が無い
120
+
121
+ - PrepareサポートなDBではかなり性能が向上する
122
+
123
+ - ナイーブなエラーのハンドリング実装を追加しやすい
124
+
125
+ - コネクションプールによる実装(Go標準)でPrepareステートメントの消費量が抑えられる
126
+
127
+
128
+
129
+ 例えばMySQLではPrepareした性能向上効果は大きいのですが、
130
+
131
+ デフォルトで16K個のPrepareステートメントを作ろうとした時点でエラーになります。
132
+
133
+ SQLビルダーで一つのことに対しN個のPrepareステートメントを作ると
134
+
135
+ その数にコネクション数Mを掛けたNxM個のPrepareステートメントを作成してしまう可能性があります。
136
+
137
+ コネクション数やPrepareステートメント数の上限を調整するという考えは
138
+
139
+ データベースへのアクセス規模に応じてどのみち必要なのですが
140
+
141
+ わざわざこれらの問題を呼び寄せてしまうバルク操作はやはりお勧めしません。
142
+
143
+ (LL言語実装の多くは並列にDBアクセスをしないのでこの問題は起きにくいです)

1

僕の場合の実装例を追記

2020/01/27 03:22

投稿

nobonobo
nobonobo

スコア3367

test CHANGED
@@ -5,3 +5,103 @@
5
5
 
6
6
 
7
7
  GoではPreparedしたひとつインサートだけのステートメントを繰り返し呼ぶ方が確実だし、この場合SQLのパースコストが下げられて性能も良いかもしれません。
8
+
9
+
10
+
11
+ 僕が複数レコードインサートを実装する場合は以下のサンプルと同じ方法を採ります。
12
+
13
+ [https://golang.org/pkg/database/sql/#Tx.Prepare](https://golang.org/pkg/database/sql/#Tx.Prepare)
14
+
15
+ ```go
16
+
17
+ package main
18
+
19
+
20
+
21
+ import (
22
+
23
+ "context"
24
+
25
+ "database/sql"
26
+
27
+ "log"
28
+
29
+ )
30
+
31
+
32
+
33
+ var (
34
+
35
+ ctx context.Context
36
+
37
+ db *sql.DB
38
+
39
+ )
40
+
41
+
42
+
43
+ func main() {
44
+
45
+ projects := []struct {
46
+
47
+ mascot string
48
+
49
+ release int
50
+
51
+ }{
52
+
53
+ {"tux", 1991},
54
+
55
+ {"duke", 1996},
56
+
57
+ {"gopher", 2009},
58
+
59
+ {"moby dock", 2013},
60
+
61
+ }
62
+
63
+
64
+
65
+ tx, err := db.Begin()
66
+
67
+ if err != nil {
68
+
69
+ log.Fatal(err)
70
+
71
+ }
72
+
73
+ defer tx.Rollback() // The rollback will be ignored if the tx has been committed later in the function.
74
+
75
+
76
+
77
+ stmt, err := tx.Prepare("INSERT INTO projects(id, mascot, release, category) VALUES( ?, ?, ?, ? )")
78
+
79
+ if err != nil {
80
+
81
+ log.Fatal(err)
82
+
83
+ }
84
+
85
+ defer stmt.Close() // Prepared statements take up server resources and should be closed after use.
86
+
87
+
88
+
89
+ for id, project := range projects {
90
+
91
+ if _, err := stmt.Exec(id+1, project.mascot, project.release, "open source"); err != nil {
92
+
93
+ log.Fatal(err)
94
+
95
+ }
96
+
97
+ }
98
+
99
+ if err := tx.Commit(); err != nil {
100
+
101
+ log.Fatal(err)
102
+
103
+ }
104
+
105
+ }
106
+
107
+ ```