1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Go/pgx】なぜQueryはCloseが必要で、QueryRowは不要なのか

Posted at

なぜQueryはCloseが必要で、QueryRowは不要なのか

Go言語で database/sqlpgx を使ってDB操作をする際、Query を使ったら必ず rows.Close() をすると思います.

ただ,なぜrows.Close()あまり理解していなかったため,この機会にまとめたいと思います.

1. Closeしないと起きる悲劇:コネクションプールの枯渇

結論から言うと、rows は読み取り専用ですが、Close() しないとDB接続(コネクション)が解放されず、プールに戻らないから です。

接続は有限のリソース

DBへの接続は、アプリケーション内で共有される「接続プール」によって管理されています。

// pgxpoolの設定例
poolConfig.MaxConns = 25  // 最大25接続

この場合、アプリ全体で同時に使える接続は25本までです。

枯渇のメカニズム

Query を実行すると、プールから接続を1つ「借り」ます。この接続は rows.Close() が呼ばれるまで「使用中」の状態になります。

もし Close() を忘れると、リクエストが終わっても接続は「借りっぱなし」になります。これを25回繰り返すと、26回目のリクエストは接続を確保できず、タイムアウトconnection pool exhausted エラーで死にます。

2. なぜQueryは自動で接続を返さないのか?

「データを取得し終わったら、ライブラリが勝手に接続を返せばいいのでは?」と思うかもしれません。
しかし、自動で返すことはできません。 なぜなら、Queryストリーミング処理(遅延読み込みを行っているからです。

一括取得ではなく、1行ずつ取得している

db.Query("SELECT * FROM users") を実行した瞬間、全てのデータがメモリに乗るわけではありません。

rows, _ := db.Query("SELECT * FROM users") 
// ★この時点ではまだ接続を保持し続ける必要がある!

for rows.Next() {
    // ここで初めてDBから行データを取得(Fetch)する
    rows.Scan(&id, &name)
}
// ループ中も、次の行を取るために接続はずっと必要

rows.Close() 
// ★ここで「もうこれ以上データはいらない」と宣言して初めて接続を返せる

PostgreSQLなどのRDBMSは、ネットワーク効率やメモリ効率のためにカーソル(またはそれに準ずる仕組み)を使い、必要に応じてデータ転送を行います。

もし Query の時点で勝手に接続を返してしまったら、rows.Next() で次の行を取りに行こうとしたときに「接続がない」状態になり、データが取れなくなってしまいます。

つまり、「いつ読み込みが終わるか」を知っているのはプログラマーだけ なので、プログラマーが明示的に Close() を呼ぶ必要があるのです。

3. なぜ QueryRow は Close が不要なのか?

一方で、1行だけ取得する QueryRow には Close メソッドが存在しません。

var name string
err := db.QueryRow("SELECT name FROM users WHERE id=$1", 1).Scan(&name)
// Close() は不要(というか書けない)

なぜこちらは不要なのでしょうか?
それは、Scan() メソッドの内部で自動的に Close しているから です。

QueryRow は「必ず最大1行だけ取得する」という仕様です。そのため、Scan が呼ばれた時点で「データの取得は(成功しても失敗しても)これで終わり」と確定します。

「読み込みの終わり」が明確であるため、ライブラリ側で自動的に Close を呼ぶことができる のです。

まとめ

メソッド 取得する行数 Closeの必要性 理由
Query 0行〜無限 必要 (defer rows.Close()) プログラマが Next() で回し続ける間、接続を維持する必要があるから。
QueryRow 0行 or 1行 不要 Scan() 実行時に、内部でデータの取得と同時に接続の解放(Close)を行ってくれるから。

Query を使うときは、読み取り専用であっても「コネクションという電話回線を繋ぎっぱなしにしている」とイメージしてください。使い終わったら必ず受話器を置く(Close する)必要があります。

1
1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?