トランザクション
複数の操作をひとまとまりとして扱う。 全部成功するか、全部無かったことになるか。
sql
BEGIN;
UPDATE accounts SET balance = balance - 1000 WHERE id = 1;
UPDATE accounts SET balance = balance + 1000 WHERE id = 2;
COMMIT;片方だけ実行されると、お金が消える。
並行実行の問題#
| 現象 | 内容 |
|---|---|
| ダーティリード | コミットされていない変更を読む |
| 反復不能読み取り | 同じ行を 2 回読んで値が違う |
| ファントムリード | 同じ条件で 2 回読んで行数が違う |
| 更新の喪失 | 2 つの更新のうち片方が消える |
| 書き込みスキュー | 個別には正しいが、合わせると制約違反 |
分離レベル#
| レベル | ダーティ | 反復不能 | ファントム |
|---|---|---|---|
| READ UNCOMMITTED | 起きる | 起きる | 起きる |
| READ COMMITTED | 防ぐ | 起きる | 起きる |
| REPEATABLE READ | 防ぐ | 防ぐ | 起きる※ |
| SERIALIZABLE | 防ぐ | 防ぐ | 防ぐ |
※ PostgreSQL の REPEATABLE READ(スナップショット分離)は ファントムも防ぐが、書き込みスキューは防がない。
分離レベルは正しさと性能のトレードオフ。 既定は PostgreSQL が READ COMMITTED、 MySQL (InnoDB) が REPEATABLE READ。 製品によって既定も意味も違うので確認が要る。
MVCC#
多版型同時実行制御。 行を上書きせず新しい版を作る。
読み手 … 自分が開始した時点の版を読む
書き手 … 新しい版を作る読みが書きをブロックせず、書きが読みをブロックしない。 これが現代の DB の高い並行性を支えている。
代わりに古い版の掃除(PostgreSQL の VACUUM)が要る。
デッドロック#
複数のトランザクションが互いのロックを待つ。 DB は検出して片方を中断する。
一般論と同じく、 アクセス順序を揃えるのが最も有効な対処。
参考文献#
- Martin Kleppmann. Designing Data-Intensive Applications. O’Reilly, 2017.
- Hal Berenson et al. A Critique of ANSI SQL Isolation Levels. SIGMOD, 1995. https://doi.org/10.1145/223784.223785
- PostgreSQL Documentation. Transaction Isolation. https://www.postgresql.org/docs/current/transaction-iso.html