@kyanny's blog

My thoughts, my life. Views/opinions are my own.

Go database/sql tutorial

Go database/sql tutorial をやった。

  • database/sql パッケージはデータベースドライバのパッケージと一緒に使う
  • *sql.DB 型のメソッドは大抵の場合 Error が返り値にあるので、必ずエラーの有無をチェックして適切な処理をすること。無視してはいけない
  • sql.Open() しただけではデータベースとの接続は行われない。実際にクエリが実行されるとき初めて接続が行われる。クエリ実行前に疎通確認したい場合は db.Ping() がある
  • sql.DB オブジェクトは頻繁に Open(), Close() するものではない
  • 結果セットを返すクエリは Query で始まるメソッドを使う(SELECT など)
  • 結果セットを返さないクエリは Exec で始まるメソッドを使う(INSERT, DELETE など)
  • 返却された行からカラムのデータを読み込むには Scan() を使う
  • SELECT 文のプレースホルダー記号はデータベース製品によって違う
    • MySQL は WHERE id = ?
    • PostgreSQL は WHERE id = $1
  • Scan() の引数はプレースホルダーの個数と同じでないとエラーになる
  • Scan() は適切な型変換を行う
  • プリペアドステートメントも使える
  • 一行だけしか返さないクエリは QueryRow() が使える
  • トランザクションは db.Begin() で開始。 tx.Commit()tx.Rollback() で終了
  • BEGIN, COMMIT ステートメントを直接実行せずに db.Begin(), tx.Commit() を使うこと
    • Tx オブジェクトと、そのトランザクションが使っているデータベース接続が占有されたままになり、コネクションプールに返却されない
  • database/sql パッケージの制約?で、トランザクションは一つのデータベース接続しか使わない
    • なので、トランザクション内で複数のクエリを連続実行する場合、先に実行したクエリを Close() しないといけない
      • この辺りピンときてない
  • db.Query()tx.Query() などを混同して使わないこと
    • トランザクションレベルで実行されるクエリと、別のデータベース接続レベルで実行されるクエリを混同しないこと
  • QueryRow() は結果が 0 行のときに sql.ErrNoRows という特別な型のエラーを返す
    • 結果が 0 行なのはアプリケーション層にとっては「エラー」ではないが、アプリケーションから判別できるようにするためにあえてそういうデザインにしているらしい
  • NULL の可能性があるカラムの値を取得する時は sql.NullString のような NULLable な型を使う
  • SELECT * FROM ... のように返却されるカラム数が不明な場合は Columns() が使える
    • が、 Go なり database/sql なりの思想として、そういうクエリはなるべく避けて、明示的にカラムを SELECT に列挙するようなのが好ましいとされてそう
    • db.SetMaxIdleConns() などでコネクションプールの設定を制御できる

いろいろ注意点が多そう。とにかく偏執的なくらいにエラーチェックをすること、トランザクションの使い方などしっかり仕様(ドキュメント)を読み込んで理解して使うこと、などが肝要なようだ。

ある操作をしたい時、やり方がたかだか一通りか二通りくらいしかない、みたいな厳密さは Go そのものの思想なのだろう。窮屈さは感じるものの、手堅さは好ましいとも思う。

写経した内容を https://gitlab.com/kyanny1/go-database-sql-tutorial に置いた。後半は説明だけだったり、実行に適さないコード例ばかりで、写経すべき内容が少なかった。