トランザクション
トランザクションとは
- 複数のSQL文をひと塊として扱うように指示できる。⇒原子的になる!!
(原子性(Atomicity)とはそれ以上分割できないこと) - データベースの整合性を担保できる。
トランザクションの4つの性質(ACID性質)
A: 原子性(Atomicity)
- トランザクションにおいて、必ず「全ての実行が完了している」か「一つも実行されていない」を保証する。(All or Nothing)
- 原子性確保のしくみ
BEGIN; --トランザクション終了 -- do something COMMIT; -- トランザクション終了
トランザクションの実行が失敗した場合、ROLLBACK
が発生する。
C: 一貫性(Consistency)
トランザクションの終了状態に関わらず、データベースの整合性が保証されなければならない。
I: 独立性(Isolation)
トランザクションを複数同時に実行しても、単独実行の場合と同じ処理にならなければならない。
D: 永続性(Durability)
トランザクション完了後の結果は、障害が発生しても失われてはいけない。
同時実行における弊害と解決策
弊害
ダーティーリード(Dirty Read)
まだコミットされていない変更を読み取ってしまうこと。
未確定の情報を元に別の処理を行なってしまうため、非常に危険な副作用
!!
反復不能読み取り(Non Repeatable Read)
トランザクションAで複数回のSELECT文を実行途中で、トランザクションBでUPDATE文でコミットしたとする。 そうするとトランザクションAでトランザクションBの結果を読み取ってしまうこと。
ファントムリード(Phantom Read)
トランザクションAで複数回のSELECT文を実行途中で、トランザクションBでINSERT, DELETE文で追加、削除を行なったとする。 そうすると処理の結果が変わってしまう問題が発生してしまうこと。
解決策: ロック(排他制御)
独立性(Isolation)を維持するためのしくみ。
あるトランザクションが現在読み書きしているレコードに鍵をかけ、他のトランザクションから読み書きできないようにする。
そのため、ロックされている間、他の処理は待たされることになる。
ロックが大量に発生すると、データベースの動作が重くなるので注意が必要。
分離レベル
前述したように正確なデータ操作とパフォーマンスは二律背反に関係にある。
そのため、分離レベル
を指定することで制御できる。
引用: トランザクション分離レベルについてのまとめ - Qiita
- MySQLのデフォルトは
REEPATABLE LEAD
- PostgreSQLのデフォルトは
READ COMMITTED
トランザクションの分離レベルの指定方法
SET TRANSACTION ISOLATION LEVEL 分離レベル名 SET CURRENT ISOLATION 分離レベル名 -- 使用する構文はDBMSによって異なる。
ロックの種類
共有ロック(shared lock)
他からの共有ロックを許す特性があるため、データの読み取り時に多く利用される。
排他ロック(excluve lock)
他からのロックを一切許可しないため、データの更新時に多く利用される。
tips: 排他ロック中でも通常のSELECT文は実行可能!!
ロックの方法
レコードロック
SELECT文で選択したレコードには自動的に共有ロック
がかかる。
しかし、末尾にFOR UPDATE
を追加すると、排他ロック
がかかる。
SELECT ~ FOR UPDATE(NO WAIT)
NOWAIT
オプションを指定した場合、DBMSはロックの解除を待機せずにすぐさまロック失敗のエラーを返すため、
トランザクションは即時終了する。これは処理を待たせたくない時などに有効。
RailsのActive recordにあるlock
メソッドを使用すると排他ロック
になる。
Item.transaction do i = Item.lock.first # ここ!! i.name = 'Jones' i.save! end
SQL (0.2ms) BEGIN Item Load (0.3ms) SELECT * FROM `items` LIMIT 1 FOR UPDATE --ここ!! Item Update (0.4ms) UPDATE `items` SET `updated_at` = '2009-02-07 18:05:56', `name` = 'Jones' WHERE `id` = 1 SQL (0.8ms) COMMIT
Active Record クエリインターフェイス - Railsガイド
テーブルロック
テーブル全体をロックする。
LOCK TABLE テーブル目 IN モード名 MODE (NOWAIT) --モード名は下記の2つ -- EXCLUSIVE 排他ロック -- SHARE 共有ロック
デットロック
上図のようにテーブルへのアクセス方向が逆の状態で同時に実行された場合、
それぞれの処理が進まない状態が発生する。これをデットロック
という。
解決策としては
- それぞれのトランザクションでロックする順番を同じにする。
- デーブル全てをロックするのではなく、可能な限りレコードブロックにする。