nownab.log

nownabe's daily posts

Golangの標準sqlパッケージ

Posted on Jan 12, 2017

http://go-database-sql.org/index.html

を読んだ。のでまとめてみた 😃

内容はちょっと古いっぽい。

Overview

  • sql.DBはDB connectionではない
  • sql.DBはインターフェイスの抽象であり、DBの存在である
  • sql.DBは次のことをやってくれる
    • コネクションの管理
    • コネクションプールの管理
  • コネクションはちゃんと閉じよう

Importing a Database Driver

  • driverは直接使うな

Accessing the Database

  • sql.Open()*sql.DBを返す
  • sql.Open()ではコネクションは確立しない
  • 実際に接続が必要になったとき、遅延的に接続される
  • sql.DBは長生きするようにデザインされている
  • 頻繁にOpen/Closeするな

Retrieving Result Sets

  • database/sqlの関数名は大きな意味を持つ
  • 例えばQueryって単語を含んでいれば、DBに問い合わせて行のセットを返す

Fetching Data from the Database

  • 結果を受け取るには適切な型の変数のポインタをrows.Scan()に渡す
  • db.Query()で返るrowsはコネクションを占領し続けるので適切にrows.Close()する
  • rows.Next()は最終的にはEOFになってrows.Close()呼ぶけど、ループ途中で抜けたりしたときのために
  • rows.Close()は既にクローズ済でも無害
  • とりあえずdefer rows.Close()しときましょう
  • ループの中でクエリして結果を受け取るような場合は、ループの中でdeferを使わず明示的にrows.Close()しましょう

How Scan() Works

  • Scan()使えばクエリ結果をよろしく型変換してくれる

Preparing Queries

  • 何回も同じクエリを実行するときはdb.Prepare()しましょう

Single-Row Queries

  • err = db.QueryRow().Scan()でエラーが起きると、Scan()までdeferされる

Modifying Data and Using Transactions

Statements that Modify Data

  • 行を返さないinsertとかdeleteにはExec()を使う
  • Query()は行を返さないSQLでもsql.Rowsを返し、それは実行後もコネクションを確保している

Working with Transactions

  • db.Begin()からCommint()Rollback()までが1つのトランザクションとなる
  • トランザクションの中でDbを操作しちゃダメ。Txを使う。Txはトランザクション内にいるけど、Dbはトランザクション外
  • コネクションの状態を変えるような複数のStatementを使う時はトランザクションが必要なくてもTxが必要
    • temporary table
    • SET @var := value
    • charset, timeoutみたいな接続オプションとか
  • シングルコネクションでごにょごにょする方法がGoにはTxしかない

Using Prepared Statements

Prepared Statements And Connections

  • Prepared Statementは、特定のコネクションと紐づく
  • database/sqlでは、statementがどのコネクションと紐付いているかをStmtオブジェクトが記憶している
  • Stmtを実行するとき、Stmtがそのコネクションを使おうとする
    • もしコネクションが閉じてたり、busyだった場合は他のコネクションに再度prepareし直す
  • こうすることでhigh-concurrencyを実現してる

Avoiding Prepared Statements

  • db.Query(sql, param)でも背後でPrepared Statementしてる
  • Prepared Statementが好ましくないときもある
    • RDBMSがサポートしてないとき
    • パフォーマンスが厳しいとき(セキュリティは別の方法でなんとかする)
  • Prepared Statementしたくないときは、fmt.Sprint()とかで整形して、db.Query()とかに渡す

Prepared Statements in Transactions

  • Txで作られたPrepared StatementsはそのTxにのみ紐づく
  • 同様にDB上で作られたPrepared Statementはトランザクション内では使えない
  • トランザクション外で準備されたPrepared Statementを使うにはTx.Stmt()を使う
  • あんま使わない方がいい (今も?)

Parameter Placeholder Syntax

Handling Errors

  • database/sqlのほぼすべての機能は返り値でエラーを返すから無視すんなよ

Errors From Iterating Resultsets

  • ループ後rows.Err()で確認する
  • 異常終了した時自動でrows.Close()される

Errors From Closing Resultsets

  • rows.Next()が最後までいくと勝手にrows.Close()されるけど、途中で抜けた時のために明示的にループ後でrows.Close()する
  • rows.Close()によるエラーは、何をすべきか明確でないので、ログとったりpanicしたりする

Errors From QueryRow()

  • QueryRow()で結果が空のときsql.ErrNoRowsというエラーを返す
  • 結果が空なのはエラーじゃないことがほとんどなので、適切にハンドルする必要がある

Identifying Specific Database Errors

  • エラーのハンドルはエラーメッセージじゃなくエラー番号で
  • ドライバごとに方法は異なる
  • エラー番号はデータベースによる
  • 数字をそのまま使うのは臭うので、定数を使おう

Handling Connection Errors

  • 10回を上限に自動で再接続する

Working with NULLs

  • NULLABLEな値を扱う時、database/sqlに用意されている特別な型を使う
  • トリッキーかつ将来性ないので、あんま使わない方がいい
  • デフォルト値を設定した方がいい
  • どうしてもNULLを避けられなかったら、SQLでCOALESCE()を使うのもあり

Working with Unknown Columns

  • Scan()は渡す変数の数がクエリ結果の列数と一致してないといけない
  • クエリ結果の列数がわからないときは、Columns()が使える
  • なんもわからんときはsql.RawBytesを使う

The Connection Pool

  • デフォルトで接続数に上限はない
  • db.SetMaxOpenConns(N)で接続数の上限を設定できる
  • db.SetMaxIdleConns(N)`はうまく調整する

Surprises, Antipatterns and Limitations

Resource Exhaustion

Large uint64 Values

Connection State Mismatch

Database-Specific Syntax

Multiple Result Sets

Invoking Stored Procedures

Multiple Statement Support