トランザクション

トランザクションとは

  • 複数の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)を維持するためのしくみ。
あるトランザクションが現在読み書きしているレコードに鍵をかけ、他のトランザクションから読み書きできないようにする。
そのため、ロックされている間、他の処理は待たされることになる。
ロックが大量に発生すると、データベースの動作が重くなるので注意が必要。

分離レベル

前述したように正確なデータ操作とパフォーマンスは二律背反に関係にある。
そのため、分離レベルを指定することで制御できる。

Image from Gyazo

引用: トランザクション分離レベルについてのまとめ - 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 共有ロック

デットロック

https://s3.us-west-2.amazonaws.com/secure.notion-static.com/64f9863b-abf5-448f-8b76-e433a79c78d4/Untitled.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAT73L2G45O3KS52Y5%2F20210825%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20210825T073949Z&X-Amz-Expires=86400&X-Amz-Signature=4f78f98820c9d52ac46a709572c40aab0ca60d3ac5dba2073aa7f4c333bfc246&X-Amz-SignedHeaders=host&response-content-disposition=filename%20%3D%22Untitled.png%22

上図のようにテーブルへのアクセス方向が逆の状態で同時に実行された場合、
それぞれの処理が進まない状態が発生する。これをデットロックという。

解決策としては

  • それぞれのトランザクションでロックする順番を同じにする。
  • デーブル全てをロックするのではなく、可能な限りレコードブロックにする。

参考

「トランザクションのACID特性」を理解する:「データベーススペシャリスト試験」戦略的学習のススメ(21) - @IT