見出し画像

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

概要

前回に引き続き、Goでデータベース周りの操作を行う際のお作法がまとめられたサイトから学ぶ。
Go database/sql tutorial

トランザクション関連の関数と、BEGIN, COMMITのようなSQL文を組み合わせて使わないでください

期待しない結果を引き起こすため、db.Begin() で取得したTx変数を取得して ExecContext() などで処理を行い、最終的に Commit() や Rollback() メソッドを呼び出してクローズする。
また一時テーブルや変数を利用する、単一のコネクションへバインドする必要がある処理の場合もトランザクション変数で処理を行う。

DB上で作成されたプリペアドステートメントは、違うコネクションに紐付けられてしまうため、トランザクション内で使うことはできません

Tx.Stmt() を使うことができるが推奨されていない。Txで作成されたプリペアドステートメントを使用するようにする。

database/sql でのほぼ全ての操作のエラーを必ずチェックし、無視しないでください

例外ではなくエラーを処理するGoの基本的お作法。最初は煩雑だし面倒だなと感じたが、慣れるとやるべき事をやっている安心感がある。
ただし一点例外があるもよう。

「全てのデータベース操作でのエラーをチェックすべきである」という一般的なルールの唯一の例外となります。
rows.Close() がエラーを返却する場合、何をすべきなのか分かりません。エラーメッセージをロギングしたり、panicするのが賢明かもしれませんし、もしそうでないならエラーを無視してください。

特定のデータベースエラーの判定

特定のデータベース処理のエラーを判定する場合は直値を扱うのではなく、それぞれのドライバーに対応した識別子などを使うようにする。
MySQLドライバーであれば MySQLError 構造体や mysqlerror パッケージになる。

NULL許可のカラムは迷惑で、汚いコードにつながります。できれば使用を避けてください

迷惑でというところがいい。〜した方がいい、だとしなくても許される余地が感じられるが、迷惑となると、わざわざ迷惑なことをしようとは思わなくなる強制力がある。
もしNull許可を利用せざるを得ない場合は COALESCE を用いる。

rows, err := db.Query(`
	SELECT
		name,
		COALESCE(other_field, '') as other_field
	WHERE id = ?
`, 42)


コネクションプールで知っておくとよいこと

・1つのデータベース上で2つの連続したステートメントを実行すると、2つのコネクションをオープンし、別々に実行する可能性がある

・コネクションは必要に応じて作成される

・デフォルトではコネクション数に制限は無く、一度にたくさんのことをする場合は「too many connections.」等のエラーを引き起こす可能性がある

・db.SetMaxOpenConns(N) を使ってデータベースへの全コネクション数を、db.SetMaxIdleConns(N) を使ってプール内のアイドルコネクションの数を制限することができる

リソースの枯渇

ここまで記載した注意点。Open ()\Close() を頻繁に行う、プリペアドステートメントでは実際に幾つものコネクションが生成されていること、SELECT以外の行を返却しないSQLに対して Query() を使用するとコネクションを保持したままになってしまうなど。

uint64 の大きな数値は扱えない

_, err := db.Exec("INSERT INTO users(id) VALUES", math.MaxUint64) // エラー


コネクション状態の不一致

コネクションプールでコネクションを管理しているため、複数のSQLステートメントを実行する場合、トランザクション変数を経由しない限り複数のコネクションでそれぞれが実行される可能性がある。これは、"USE" や "BEGIN" などのコマンドとクエリが別のコネクションで実行され想定通りに動かないなどが例としてある。解決策としては、「database_name.table_name」の形にしたり、db.Begin() を使用する。
さらに注意が必要なのは、それらがコネクションプールに戻され、再度利用されることで発生する悪影響がある。

バルクコピー、ストアドプロシージャ、複数ステートメントは非サポート

中でも気をつけなければいけないのが連続したステートメントの処理。トランザクションは単一のコネクションと結びつくため、使用中の状態を保持したままかどうか意識しないと下記のように問題となってしまう。

tx, err := db.Begin()
rows, err := tx.Query("select * from tbl1") // txのコネクションを使用
for rows.Next() {
	err = rows.Scan(&myvariable)
	// エラー! txのコネクションは既に「ビジー状態」です
	tx.Query("select * from tbl2 where id = ?", myvariable)
}

あとがき

データベースの扱いのチュートリアルだが、プログラミングの基本的な考えとして参考になるところが幾つもあった。
詳細な設計を隠す、依存しないことで拡張出来るようにする、エラーを判定する、直値を扱わない、複雑な処理を裏で行い簡易に扱えるようにする、またコストが掛からないようにする。
Golangでデータベースを扱う際には必須の知識と思われる。「迷惑」な利用をしないように、これらの教えを守っていきたい。

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