以下のようなコードを書いた。
func UpdatePrices(priceIds []int, orderKey string) {
psqlconn := psqlConnect()
// open database
db, err := sql.Open("postgres", psqlconn)
checkError(err)
// close database
defer db.Close()
for i, id := range priceIds {
// update
updateStmt := fmt.Sprintf(
"update prices set %s = %v where id = %v",
orderKey,
i+1,
id,
)
_, err := db.Query(updateStmt)
if err != nil {
log.Printf("Error update recode %d: %v", id, err)
}
}
}
やっていることは、 prices テーブルの引数に応じたカラムをアップデートする。
ただ、97レコード目をアップデートしようとした時に以下エラーが出た。
pq: sorry, too many clients already
postgres には、コネクション数に上限あるらしく、それを超えたらしい。
ChatGPT に聞いてみた。
このエラーは、クライアントが多すぎるためにPostgreSQLが新しい接続を拒否していることを示しています。コードで各クエリ実行ごとに新しいデータベース接続を開いているため、コネクションプールが枯渇してしまうのです。
sql.DB
自体は接続プールを管理しますが、db.Query
は実行後に結果セットを閉じないと接続が解放されません。この問題を解決するには、以下のように接続を効率的に管理し、不要な接続が解放されるようにします。
修正案
- データベース接続を関数の最初で1回だけ開きます。
- 各クエリ実行後に結果セットを閉じて接続を解放します。
Query
ではなくExec
を使用します。Exec
は結果セットを返さないため、クエリ実行後に結果セットを閉じる必要がありません。
どうやら、 db.Exec
を使うと良いらしい。
修正してみたコードが以下。
func UpdatePrices(priceIds []int, column string) {
psqlconn := psqlConnect()
// open database
db, err := sql.Open("postgres", psqlconn)
checkError(err)
// close database
defer db.Close()
for i, id := range priceIds {
// update
updateStmt := fmt.Sprintf("update prices set %s = $1 where id = $2", column)
_, err := db.Exec(updateStmt, i+1, id)
if err != nil {
log.Printf("Error update recode %d: %v", id, err)
}
}
}
別途調べると、 db.Query
は rows の戻り値を期待し続けるため、コネクションが維持され続けてしまうらしく、 row を返さない場合 db.Exec
を使用するらしい。