はじめに
GORMでDB操作していて、トランザクションをやりたかったので調べたときの備忘録です。細かいとこ間違っていたらすみません。
トランザクションとは
データベースへの複数操作(レコードの読み書きなどの複数SQL文)を1つのステップにまとめるための仕組み。
トランザクションを開始し、レコードを書き換え、処理後にコミットするという流れで行われる。トランザクションに関わるデータはコミットするまでロックされる。
トランザクションの特性
4つの特性があり、頭文字をつなげてACIDと呼ばれる。
- 原子性(Atomicity)
トランザクション内で行われる処理はすべて成功するか、すべて失敗するかのどちらかになる。
処理の途中で失敗した場合、それ以前の成功は無かったことになり、すべて失敗したものとして一連の処理が終了する。 - 一貫性(Consistency)
トランザクションの前後で、データベースの整合性や制約など、正常な状態が失われないことを保証する。 - 分離性(Isolation)
並行して実行される複数のトランザクションは、お互いに干渉せずに実行でき、あたかも同時に実行されるトランザクションが存在しないように振る舞う。ただ、本当に同時に実行しないとなると処理速度が低下するため、分離レベルを弱くして対応する場合もある。 - 永続性(Durability)
トランザクションがコミットされたとき、その状態が半永久的に保持されること。
※分離レベル(下ほど強い)
- 未コミット読み取り(read uncommitted)
最弱。ダーティリード(未コミットの他トランザクションの変更を読める)、ノンリピータブルリード(他トランザクションのコミットによって読み込む値が変化する)、ファントムリード(他トランザクションが追加/削除した場合、読み込むデータが変わる)をすべて許容する。 - コミット済読み取り(read committed)
ノンリピータブルリード、ファントムリードを許容し、ダーティリードを許容しない。 - 反復可能な読み取り(repeatable read)
ファントムリードのみを許容する。 - 直列化可能性(serializability)
最強。3つすべて許容しない。
GORMでトランザクションを実装する
概ね、公式のリファレンスに書いてあります。
db.Transaction(func(tx *gorm.DB) error {
// トランザクション内でのデータベース処理を行う(ここでは `db` ではなく `tx` を利用する)
if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
// 何らかのエラーを返却するとロールバックされる
return err
}
if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil {
return err
}
// nilが返却されるとトランザクション内の全処理がコミットされる
return nil
})
自分「あれ、分離レベルはどうするの...?」
GORMでトランザクション(分離レベル付)を実装する
GithubでTransaction関数の実装を読んでみました。
それによれば、どうやら可変長引数*sql.TxOptionsがあるようです。
func (db *DB) Transaction(fc func(tx *DB) error, opts ...*sql.TxOptions) (err error) {
panicked := true
if committer, ok := db.Statement.ConnPool.(TxCommitter); ok && committer != nil {
// 以下略
つまり、分離レベルを使用したTransactionの実装はこのようになります。
import(
"database/sql"
)
db.Transaction(func(tx *gorm.DB) error {
// トランザクション内でのデータベース処理を行う(ここでは `db` ではなく `tx` を利用する)
if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
// 何らかのエラーを返却するとロールバックされる
return err
}
if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil {
return err
}
// nilが返却されるとトランザクション内の全処理がコミットされる
return nil
}, &sql.TxOptions{Isolation: sql.LevelSerializable})
sql.Txoptionsはdatabase/sqlパッケージで定義されている構造体です。
type TxOptions struct {
// Isolation is the transaction isolation level.
// If zero, the driver or database's default level is used.
Isolation IsolationLevel
ReadOnly bool
}
IsolationLevelで設定可能な分離レベルは以下の通りです。
const (
LevelDefault IsolationLevel = iota
LevelReadUncommitted
LevelReadCommitted
LevelWriteCommitted
LevelRepeatableRead
LevelSnapshot
LevelSerializable
LevelLinearizable
)
おわり
GORMの公式ドキュメントの日本語版だけではなく、英語版にもこのことが書かれていないため、おそらく忘れられているのだと思います。多分、気が向いてオライリーの本を読まなければ、分離レベルの存在に気づかなかったので、オライリーの本様様といった感じです。
参考
database/sqlパッケージのドキュメント
オライリーの本こと詳説データベース。中々面白かったです。