見出し画像

【Go初学】Go database/sql tutorial [1]

概要

MySQL周りなどをググっている時に、Goでデータベース周りの操作を行う際のお作法がまとめられたサイトを知った。
Go database/sql tutorial
こちらを見ていて気になったものを覚書として記述する。

ドライバパッケージを直接使わないでください

MySQLやPostgresQLなどの各データベースドライバへ依存せず、差し替えが容易になるお作法。言語のお作法レベルで共有されているのは他に見ない気がするのでよい文化だなと思う。

sql.Open()はデータベースへの接続を確立しません

抽象化されたデータベースの準備をするだけです。
実データストアへの実際のコネクションは、必要になった時に初めて、遅延して確立されるでしょう。

アプリケーション起動時にデータベースが利用可能か調べるには Ping() でチェックしておくことが必要。

err = db.Ping()
if err != nil {
  // do something here
}


Open()とClose()を頻繁に行わないでください

最初の実装時、Open()したはいいがいつClose()するのだろうと思った。長期間使われることを想定して設計されており、プログラムの最後にClose()する形が望ましいもよう。

必要に応じてそのsql.DBを引き回すか、あるいはどうにかグローバルに利用可能にできるようにしてください。ただしClose()せずに開いたままにしておいてください。

何度も使うクエリは常にプリペアにするべきです

db.Query()の裏側で、実際にはクエリのプリペアをし、クエリを実行し、そしてプリペアドステートメントをクローズしています。それはデータベースを3往復しています。もしあなたが注意深い人間でないならば、アプリケーションとデータベースとのやりとりを3倍にしてしまうでしょう!

下記のようなbenchmarkで単純なクエリを比較したところ、db.Prepare()よりdb.Query()の方が10%弱早かった。複雑な条件やレコード数が大きい場合に Prepare() の方が効果的に働いてくるのだろうか。

SELECT * FROM users WHERE id = ?


行を返却しないSQLステートメントでは、Exec()を使います

_, err := db.Exec("DELETE FROM users")  // OK
_, err := db.Query("DELETE FROM users") // NG

Query()をこのように使ってはいけません。Query()はsql.Rowsを返却しますが、これはクローズされるまでデータベース・コネクションを保ちます。
上記の例では、コネクションはずっと解放されないでしょう。このアンチパターンはリソース枯渇(例えばコネクション超過)の典型例となります。

明示的に使い分ける必要があるので注意。ちょうど↑の Prepare() と Query() の検証の際、QueryRow() を使っていたら "Too many connections" エラーとなりベンチマークが測れなかった。

for i := 0; i < b.N; i++ {
  _ = mydb.Db.QueryRow("SELECT * FROM zos WHERE id = ?", 1)
}

戻り値のRowは不要と破棄していたが、上記注意書きの通りコネクションが解放されないまま数百、数千と実行しようとしてエラーとなったもよう。
Rowには Close() が無いからどうしたものかと思ったが、ソース(sql.go)を見ると Scan() 内部で Close() していたため Scan() を入れたところ解消した。
普通は Scan() もセットで実行するので問題にならない。特殊な使い方をしたおかげで注意点を学ぶ形になった。

あとがき

チュートリアルの半分までを確認した。よいお作法を学んで効率良く不具合を生まない設計で書いていきたい。

※チュートリアルは英語で書かれておりGoogle翻訳で大体読めるが、日本語訳してくださっているこちらのサイトが分かりやすい。



この記事が気に入ったらサポートをしてみませんか?