・はじめに
この記事は、サポーターズ主催のCARTA HOLDINGS社による『DBのロックについてあまり意識したことがない人に向けた、実は覚えておきたいロックについての知識』に参加したので、それについてのアウトプットの記事です。・ロックとは
DBにおけるロックとは「データのアクセスから処理までの一連の流れが終わるまで、他のプログラムはそのリソースにアクセスすることができないようにすること」をいいます。これは複数のプログラムが同じリソースにアクセスをした際に起こってしまう不具合を防ぐために行います。・具体例
- Aさんが口座の残高の情報を取り出す。残高である1000を残高変数に代入する。
- Bさんも口座の残高の情報を取り出す。残高である1000を残高変数に代入する。
- Aさんが口座から1000円引き出す。残高変数から1000が引かれる。残高変数は0円
- Aさんの行った引き出しが残高に反映され、残高は0円となる。
- Bさんが口座に1000円入金する。残高変数から1000円が足される。Bさんは13:05時点の残高の情報しか持っていないのでもともとの残高変数である1000円に今回入金された1000円が追加され、合計2000円になる。
- Bさんの行った入金はAさんの行った引き出しに上書きされる形で反映されるため、この口座の残高は2000円となってしまう。
- Aさんが口座の残高の情報を取り出す。残高である1000を残高変数に代入する。
- Aさんがロックをかけたため、Bさんは口座の残高の情報を取り出せない
- Aさんが口座から1000円引き出す。残高変数から1000が引かれる。残高変数は0円
- Aさんの行った引き出しが残高に反映され、残高は0円となる。反映が終わったためロックが解除される。
⇒Bさんはここではじめて口座にアクセスすることができる。
このようにDBのロックは、一つの処理が終わるまでずっとそのリソースにアクセスできないようにすることによって複数のプログラムが同じリソースにアクセスした際に起こり得るバグを事前に防ぐことができます!
しかし、ロックにもデメリットがありロックがかかることによって待機時間が増え、プログラムの実行時間が長くなってしまいます。さらにロックの設計にミスがある場合、ロック待ちが永遠と続いてしまうデッドロック状態になってしまう可能性もあります。
・共有ロックと排他ロックとは
共有ロックとは、ロックと名のついている通りアクセスにロックをかけるものですが、他のプログラムも共有ロック処理を付けてプログラムを実行した時はロックに引っかからずに実行できる、共有ロック同士は実行できるというロックのことです。対して排他ロックは、かけた場合いかなるアクセスも許さないようにするロックのことです。このロックはどうしても値にバグが起きてしまうと困る場合、金銭の処理やポイントの処理などに使われます。
MySQLの機能でSELECTする際は共有ロックがかけられ、INSERT、UPDATE、DELETEする際は排他ロックがかけられるようになっています。
・トランザクション分離レベルとは
トランザクション分離レベルとは、ロックがどれほど他の処理に影響するか、それによって起こり得る不具合を表すものです。これはデータベースを設計する際に考えることになるACID特性のIsolation(=分離性)に関係する部分です。このトランザクション分離レベルによって、引き起こしてしまう不具合の数が変わります。トランザクション分離レベルで起こる不具合の種類は3種類あり、ダーティーリード、非リピータブルリード、ファントムリードの3つです。
・ダーティーリードとは
ダーティーリードとはロックがされていない(分離性が低い)時に発生する不具合で、別の処理でまだコミット(結果を反映)していない内容を読み取ってしまうこと。・非リピータブルリードとは
非リピータブルリードとは別の処理で更新された値が即座に反映されてしまうこと。例えば2回データを読み取る必要がある処理を行う際、1回目のデータが2回目のデータが異なるという不具合です。・ファントムリードとは
ファントムリードとは別の処理で追加/削除された値がすぐに反映されてしまうこと。例えば2回データを読み取る必要がある処理を行う際、1回目になかったデータが2回目に表れてしまうという不具合です。・READ UNCOMMITTED
READ UNCOMMITTEDは分離性が一切ないレベルのことです。このレベルにおいてはロックがかけられていないのでデータの整合性は取りずらいですが、ロック待ちが起こらないので素早くデータのやり取りが行えるというメリットが存在します。これは書き込みしかしないログを記録するアプリやデータの更新頻度が低いアプリで使われます。 このレベルでは先ほどのダーティーリード、非リピータブルリード、ファントムリードのすべての不具合が起こってしまう可能性があります。・READ COMMITTED
READ COMMITTEDは、コミットしていないときは影響は及びませんが、コミットをしたときは別の処理に影響を及ぼしてしまいます。このレベルでは非リピータブルリード、ファントムリードの2つの不具合が起こってしまう可能性があります。・REPEATABLE READ
REPEATABLE READとは、更新が別の処理でコミットされても分離性を保てる分離レベルです。MySQL(InnoDB)においては、Consistent reads(MVCC)という技術が使われるため、このレベルではファントムリードの発生を抑えてくれます。Consistent readsとはデータを読み取った際、そのデータを覚えることによって他の処理でデータが更新、追加された場合でも、1回目に読んだデータと違うという不具合が起きません。・Serializable
Serializableとはダーティーリード、非リピータブルリード、ファントムリードのすべての不具合が起こる可能性がありません。しかしデッドロックなどによってロック待ちが引き起こり、処理時間が長くなってしまう可能性が高くなります。・ロックの範囲の種類
ロックをかける範囲の種類には4つあります。1つ目はレコードロックです。レコードロックはその名の通り1つのレコードにロックをかけるもので、範囲が狭いものになります。2つ目はギャップロックです。ギャップロックは、指定した範囲にロックをかけるもので、WHERE文で指定した範囲をロックするものです。
3つ目はネクストキーロックです。これは指定されている範囲(ギャップロックの範囲)に加えて、範囲+1のキーのロックをするというものです。この番号をロックしたってことは次のこの番号も使うでしょといってロックをかけるものです。
最後は空振りロックで、存在しないキーのロックをしてしまうものです。存在しないキーのロックをしてしまうと処理が永遠に終わらない判定となり、ロックが終了せずデッドロックを引き起こす原因となってしまいます。これを引き起こさないためには、予めDELETE文やUPDATE文を発行する際、対象となっているキーが存在するかを確認することが重要になります。
・まとめ
このようにロックとは「データの整合性を保つためにアクセスの制限をすること」であり、ロックには共有ロックと排他ロックの2種類存在します。そしてロックの影響の度合いを表すトランザクション分離レベルとそれぞれのレベルで引き起こされる不具合であるダーティーリード、非リピータブルリード、ファントムリードの3種類あります。