TOP

データベースsqlite3 その7

同時実行制御 トランザクションについて

ファイルロックと同時制御

データベースにデータを格納する処理の利点は、ファイルにデータを格納するのと違い、データを安全に処理する仕組みが備わっているという点にあります。レ コードを挿入・変更・削除するときに、別プロセスからの処理に干渉されないように、データの整合性を保つます。処理中に障害が発生したときに、障害発生前 に安全に戻せるような仕組みなどが、これにあたります。

データベースのACID特性

データベースによる性能評価の基準として以下のようなものがあります。

Atomicity 原子性

データベースの処理がすべて実行されるか、障害により、すべてが実行直前の状態に戻るかになることを保証します。

Consistency 一貫性

データの更新によって、どこからアクセスしても、同じデータが得られることを保証する。

Isolation 分離性

複数のデータベース処理が干渉しあうことが無いことを保証する。

Durability 持続性

データベース処理実行後はその後の障害があっても、損なわれることは無い。

同時実行制御

複数のプロセス(スレッド)が単一のデータベースにデータを格納する為には、sql文が同時に投入されたときに制御する仕組みが必要となってきま す。格納処理は最終的には、一つずつ順番に処理しなければ、正しく格納される保証が無いからです。また、格納処理にエラーが出てしまえば、同時に処理され るデータの整合性が失われてしまう可能性があります。sqlite を含めRDMS といわれるデータベースエンジンには、これを制御する同時実行制御がそなわっております。このことは、上記の分離性の保証にかかわっております。

そして、この仕組みを実現させるために、トランザクション処理が備わっております。

同時実行によるデータの一貫性や分離性が損なわれる問題

同時実行制御するにあたって、以下のようなことを留意する必要があります。

  1. コミットされていないデータを別のトランザクションから参照できてしまう。DirtyRead
  2. 別のトランザクションによる書き込みで, 更新したデータが上書きされてしまう。LostUpdate
  3. トランザクション内で別のトランザクションによるデータ更新が反映されてしまい, 同じレコードでも参照するたびに結果が変わる。FuzzyRead
  4. 同じトランザクション内でも, データの参照結果が増減する. 別のトランザクションによるデータ更新 or 削除が参照できることが原因で発生する。PhantomRead

トランザクション処理について

トランザクションとは、一連のデータベース操作を1つのまとまりの処理として実行できる機能です。一つのまとまりとしての処理は以下のような構成になっております。

  1. 外部キー制約が有効になっている場合は、PRAGMA foreign_keys=OFF を使用して無効にします。
  2. BEGIN TRANSACTION or BEGIN トランザクション開始
  3. ジャーナルファイルを生成(または生成されている場合もあり。)し、コミットが済んでいるデータ情報を書き込む(先行書き込み WAL)。
  4. 書き込みステートメント発行 (CREATE、DELETE、DROP、INSERT、UPDATE など)。
  5. ROLLBACK または COMMIT sql文を発行する。
  6. ROLLBACK の場合は、ジャーナルファイルを使って、トランザクション開始前の状態に戻します。COMMIT の場合は、ジャーナルファイルモードにしたがって、ファイルを書き換えまたは削除します。
  7. 外部キー制約が有効になっている場合は、PRAGMA foreign_keys=ONに戻します。

ロックによる同時実行制御の実現

ロックとは、あるリソース、ここではトランザクションに対して、鍵をかけ、他のトランザクションが実行できない(待機させる)ようにすることです。sqlite では以下のようなロック状態をがあります。

UNLOCKED ロック解除

ロックを保持していない状態です。どのプロセスも書き込みまたは読み込みしていないデータベースの初期状態です。

SHARED 共有

データを読み込む場合に取得される状態。この状態から書きこみステートメントを発行するとRESERVEDに移行する。複数のプロセス(スレッド)を保持できる。他のプロセスからの読み込みに対してはオープンであるため、読み込みを許可するが書き込みは許可しない。

RESERVED 予約済み

プロセスが将来書き込みを行う予定であるが、書き込みは実行されていない。読み込みは許可されている。但し、SHAREDロックから唯一のプロセス しか受け入れられないので、複数のプロセスから、同時にRESERVEDできない。RESERVED ロックがある間に新しい SHARED ロックを取得できるという点で PENDING とは異なります。

PENDING 保留中

EXCLUSIVEロックを取得して書き込みを実行するために、SHARED状態からの解放をするために待機している状態である。EXCLUSIVE以外のロック状態の取得はできない。常に EXCLUSIVE ロックへの一時的な踏み台にすぎない。

EXCLUSIVE 排他

実際に書き込み処理を実行している状態。唯一の状態なので、他のロック状態の取得はできない。同時実行性を最大化するために、SQLite は EXCLUSIVE ロックが保持される時間を最小限に抑えるように機能します。

トランザクションモード

トランザクション内で、上記のロックを取得するタイミングの違いを以下のモード設定ステートメントで制御できます。

BEGIN DEFERRED

BEGIN のみの場合も同じ。データベースが最初にアクセスされるまでトランザクションが実際に開始されないし、ロック状態も取得されない。最初のread処理でSHAREDロックを取得し、最初のwrite処理でRESERVEDロックを取得する。

BEGIN IMMEDIATE

書き込みステートメントを待たずに、データベース接続が新しい書き込みをすぐに開始するようにします。開始時にRESERVEDロックを取得する。 他のどのdatabase connectionも、DBへのwrite、IMMEDIATE/EXCLUSIVEトランザクションの開始はできなくなる。ただし、readは可能。

BEGIN EXCLUSIVE

開始時にEXCLUSIVEロックを取得する.全てのデータベースコネクションからデータを読み込むこと(read uncomitted” connectionを除く)、書き込むことが出来なくなる。

自動コミット機能

明示的にトランザクションを開始する、しないに関わらず、DBの書き込みステートメント(追加・更新・削除)ではかならずトランザクションが実行されます。

BEGIN TRANSACTIONなしでDBを更新するSQLを発行した場合には、内部で自動的にトランザクションが開始され、SQL実行後にコミットされることになります。
BEGIN TRANSACTIONを明示的に実行した場合には、COMMITもしくはROLLBACKを実行するまでトランザクション状態が継続されます。

但し、以下のsql分では、トランザクションされても、自動コミット機能が自ずと適用されているので、ロールバックできません。

  • DROP DATABASE
  • DROP TABLE
  • DROP
  • ALTER TABLE

ロールバックジャーナルファイル

データベースに障害があった場合に正常な状態に戻るために使用されるファイルです。

データベース ファイルから読み取る前に、SQLite は常にデータベース ファイルにホット ジャーナルがあるかどうかを確認します。
ト ランザクションが完了する前にアプリケーションまたはホスト コンピュータがクラッシュした場合、ロールバック ジャーナルまたは先行書き込みログには、メイン データベース ファイルを整合性のある状態に復元するために必要な情報が含まれています。ロールバック ジャーナルまたは先行書き込みログにデータベースの状態を回復するために必要な情報が含まれている場合、それらは「ホット ジャーナル」または「ホット WAL ファイル」と呼ばれます。

ジャーナルモード

ジャーナルファイルには、トランザクション処理ごとに書き込みが行われます。この処理が、膨大な回数になれば、そのコストも無視できないものになります。sqlite の場合は、pragmaコマンドを使用して設定を切り替えることが出来ます。

PRAGMA schema.journal_mode = DELETE | TRUNCATE | PERSIST | MEMORY | WAL | OFF
*schemaはデータベースファイル名(エイリアス)、単一データベース操作の場合は main

デフォルトでは delete が設定されており、トランザクション処理が開始した時に生成され、終了とともに消失してしまう。実行コストと持続性Durabilityを考えて、ファイルを残す、 TRUNCATE |あるいはPERSISに変えることもできる。MEMORYはファイルをメモリー上に保持されます。

適切にトランザクション処理をチューニングする

「同時実行によるデータの一貫性や分離性が損なわれる問題」をすべて解消すれば、データベースのACID特性は満たされることになる。ただし、パ フォーマンスは犠牲になる。処理に著しく時間がかかることになります。この問題は、使用するデータベースの特性を考慮した上で、データの一貫性や分離性を 一部犠牲にして考える必要があるといわれております。

データベースを適切に利用するための要因

  • 取り扱うデータ量
  • 書き込みトランザクションが頻繁に発生する。書き込み中心である。
  • トランザクション処理時間
  • ロック取得期間
  • ロックするデータの範囲(ロック粒度またはロックレベル)。テーブル全体, 行全体, 特定の列がある。SQLiteではデータベース全体のロック粒度しか選択できない。

パフォーマンスを下げる現象

ロックにより、同時実行制御を実現できるようなりました。しかし、その代償として以下のような現象を考えなければいけないことになっております。

スラッジング

更新ステートメントが頻繁に発効される状況において、排他ロックにより、待ち状態が頻出し、パフォーマンスが著しく低下してしまう。ロック粒度を下げるか、更新データ量を制限する等の対策が必要となってきます。

デッドロック

トランザクション内に複数の更新ステートメントがある場合、他のトランザクションが同時に投入された場合、その中の複数の更新ステートメントが、最 初に投入されたトランザクションの更新ステートメントの排他ロックをお互いに掛け合ってしまい、処理まちが永遠に続いてしまう。このような複数の更新ス テートメント実行を取り合ってしまうような場合は、データベース設計を考える必要があります。

トランザクションモードとデータの一貫性や分離性が損なわれる問題との関係

DEFERREDIMMEDIATEEXCLUSIVE
DirtyRead
LostUpdate×
FuzzyRead×
PhantomRead××

一般的には、更新処理があまりない、参照中心の場合は、一貫性・分離性は緩くし、パフォーマンスを優先するようです。逆に、更新が頻発するような場合、パフォーマンスよりは、データの整合性を重視した方向でチューニングされるべきとしています。

トランザクションをコマンドラインで検証してみる

trans_sample テーブルを作成し、first second 二つのターミナルの間で、トランザクション処理をやりあってみます。

ジャーナルモードの変更

ジャーナルファイルはデフォルトでは、delete に設定されているので、トランザクションが終了すると消失してしまいます。persist は終了時にジャーナルファイルのヘッダーの部分を0に上書きすることになり、ファイル削除にかかる処理コストの節約につながるようです。 truncate はファイルを削除するのではなく、0バイトの長さに切り捨てることにより、コスト削減する方式になります。

トランザクションに名前をつける[savepoint]

savepoint transaction-name;
 トランザクションに名前をつけ、トランザクションをロールバックするジャーナルファイルを生成
ROLLBACK TRANSACTION TO SAVEPOINT savepoint-name 
  データベースの状態を、対応する savepoint-name の直後の状態に戻します。
  トランザクション自体はキャンセルされない。
RELEASE savepoint-name
  savepoint-name を開放する。

トランザクションモード

BEGIN DEFERRED or BEGIN 無指定モード

BEGIN IMMEDIATE モード

BEGIN EXCLUSIVE

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください