database/sqlのコネクション周りの実装をちゃんと調べた
Goのプロジェクトをやってる中で、database/sqlのトランザクションがどう管理されているのか不安に思ったので、ちゃんと調べてみた。
これを参考にコードを追った。
ちなみにGoのバージョンは1.9.1
https://qiita.com/methane/items/a9803388ff4a5a985b4d
登場人物(オブジェクト)
- sql.DB
- DBを司るオブジェクト。
- sql.Tx
- トランザクションを管理するオブジェクト。
sql.DB.Begin()
すると作られる。
- トランザクションを管理するオブジェクト。
- sq.driverConn
- DBのコネクションを司るオブジェクト。コネクションプールから取ってきたコネクション情報が入ってる。
- driverStmt
- コネクションプールのステートを司るオブジェクト。こいつをみて、使用中かどうか判断する。
実行している流れ
通常のクエリ
使用時
sql.DB.Query(...)
の実行してクエリを実行- sql.Rowsのオブジェクトが帰ってくる
sql.Rows.Close()
を実行する。
内部の実行内容
- Queryの実行時
sql.DB.conn(ctx context.Context, strategy connReuseStrategy) (*driverConn, error)
が実行される- DB.freeconに使用していないコネクションオープン済みのdriverConnが入っている。
sql.DB.conn(...)
を実行すると、開いているコネクションがあればそれを返し、なかったら開いているコネクションがないか探索して、それでもなければ新しいコネクションを作って返す(MaxConnectionまでの範囲内で。)
- driverConnと使用中フラグをあげたdriverStmtをDBオブジェクトに詰める
- 詰めたDBオブジェクトをsql.Rowsに詰めて、sql.Rowsに実行結果を詰めて返す。
- sql.Rows.Close()実行時
- sql.RowsにあるDBオブジェクトから辿っていって、driverStmtの使用中フラグを下ろす
トランザクショナルなクエリ
使用時
sql.DB.Begin() (*Tx, error)
を実行する- sql.Txオブジェクトを使って一連のトランザクションでの処理を実行する
- 終わったら
sql.Tx.Commit()/sql.Tx.Rollback()
を実行する
内部の実行内容
sql.DB.Begin()
実行時sql.DB.conn(...)
を実行してdriverConnオブジェクトを取得- driverConnをと使用中フラグをあげたdriverStmtをsql.TxにつめてTxオブジェクトを返す
sql.DB.Commit()/.Rollback()
実行時- sql.Txから辿って行って、driverStmtの使用中フラグを下ろす
で、どういうことだってばよ。
通常のクエリ
- コネクションが開いているやつをコネクションプールから適当に持ってくる
- コネクションに紐つくステートメントオブジェクトの使用中フラグをあげる
- クエリを流す
- 流し終わったら(つまり、
sql.Rows.Close()
が実行されたら)、使用中のフラグを戻して、コネクションプールに返す。
この場合、常に、オープンな適当なコネクションがくるので、どのコネクションがくるかはわからない。
トランザクショナルなクエリ
- 通常と同じようにコネクションが開いているやつをコネクションプールから適当に持ってくる
- コネクションに紐つくステートメントオブジェクトの使用中フラグをあげる
- フラグをあげたコネクションオブジェクトをトランザクションオブジェクトに詰めて、トランザクションオブジェクト返す
- トランザクションオブジェクトのCommit/Rollbackメソッドが実行されたら、ステートメントオブジェクトの使用中フラグを戻してコネクションプールに返す。
参考までにgormの実装は
一部のプロジェクトではORMとしてGORMを採用しているのだが、そこでは何をやっているのか気になったので調べてみた。
結論から言うと(当たり前だけど)、gormはただ単にdatabase/sqlの各メソッドをラップしているだけなので、処理の流れは同じ。
ただ、Rows.Close()を自動でやっているだけ。
ただひとつだけ問題があって、gorm.DB自体がsql.Txとsql.DBの両方の意味をもっているということ。
どちらのオブジェクトもクエリを実行できるオブジェクトなので分けたくなかった、という意図だと思われるが、使っている側からしたらトランザクションを開始したオブジェクトなのかわかんないっていう。