iamkanae.dev

Blog

db.Query()を使った後のClose()を忘れないようにしよう

初投稿です。

Goを学び直していて、db.Query()のClose()をする理由について学んだので自分用にメモしておく。

学んだこと

db.QueryRow()やdb.Exec()の後にはClose()は不要なのに、db.Query()はなぜ必要なのか?

db.Query()はカーソルを保持し続けるから

db.Query()は複数行をストリーミングで読み込む可能性があり、メモリ効率のために全データを一度にメモリに載せないようになっている。代わりにリソースを保持し続けるようになっている。

db.QueryRow()は1行しか返さないので、Scan()を呼んだ時点でリソースが自動的に解放される。
db.Exec()はINSERT/UPDATE/DELETEの結果だけなので、Close()は存在しない。

Close()しないとどうなるか?

Close()しないとリソースリークを引き起こす。

Close()が必要な理由は、DB側のカーソルを解放し、コネクションをプールに戻すため。
Close()をしなかった場合は以下のような挙動となる。

func badExample() {
    rows, _ := db.Query("SELECT ...")
    // defer rows.Close() を忘れた
    
    for rows.Next() {
        // データ処理
    }
    
    // 関数を抜ける
    // → カーソルは閉じられない
    // → コネクションがプールに返らない
    // → 他の処理がコネクションを使えない
}

そのためbadExample()を10回呼ぶと、10個のコネクションが占有される。そしてゆくゆくはプールが枯渇しエラーとなる。

おわり

PHPの時はリクエストごとに接続が切られるからあんまりコネクションのことをそこまで意識してなかったけど、Goの場合はアプリケーションが起動されたらコネクションを使い回すから、こういうリソース管理はちゃんと意識してないと事故りそうだ。意識しておこう。