BerkeleyDB のドキュメントは、これらの実装がどのように機能するかを理解するのに実際に非常に役立つことを発見したことを覚えています。これは、リレーショナル/クエリ計画インフラストラクチャ全体なしでトランザクションを実装する非常に低レベルのデータベースであったためです。
すべてのデータベース (言及したものだけでも) がまったく同じように機能するわけではありません。PostgreSQL の最下位レベルの実装は、Oracle および SQL Server のスナップショット実装とはまったく異なりますが、それらはすべて同じアプローチ (MVCC: マルチバージョン同時実行制御) に基づいています。
ACID プロパティを実装する 1 つの方法は、ユーザー (ここでは「ユーザー」とは、変更を行うトランザクションです) がデータベースに対して行ったすべての変更を「トランザクション ログ」に書き込み、各行 (原子性の単位) を確実にロックすることです。コミットまたはロールバックするまで、他のトランザクションはそれを変更できません。トランザクションの最後に、コミットする場合は、コミットしたことを示すレコードをログに書き込み、ロックを解放します。. ロールバックする場合は、トランザクション ログをさかのぼってすべての変更を元に戻す必要があります。そのため、ログ ファイルに書き込まれた各変更には、データが最初にどのように表示されたかの「変更前のイメージ」が含まれます。(実際には、トランザクション ログもクラッシュ リカバリのために再生されるため、「アフター イメージ」も含まれます)。変更する各行をロックすることにより、トランザクションの終了後にロックを解除するまで、並行トランザクションは変更を認識しません。
MVCC は、更新によってブロックされるのではなく、行を読み取りたい同時トランザクションが代わりに「前のイメージ」にアクセスできる方法です。すべてのトランザクションには ID があり、どのトランザクションのデータを「見る」ことができ、どのトランザクションのデータを「見ることができない」かを決定する方法があります。このセットを生成するためのさまざまなルールを使用して、さまざまな分離レベルを実装します。したがって、「反復可能な読み取り」セマンティクスを取得するには、トランザクションは、たとえば、その後に開始されたトランザクションによって更新された行の「変更前イメージ」を見つける必要があります。これは単純にトランザクションにビフォア イメージのトランザクション ログを参照させることで実装できますが、実際にはそれらは別の場所に保存されます。したがって、Oracle には個別の REDO スペースと UNDO スペースがあり、REDO はトランザクション ログです。undo は、同時トランザクションが使用するブロックのビフォア イメージです。SQL Server は、変更前のイメージを tempdb に格納します。対照的に、PostgreSQL は行が更新されるたびに常に行の新しいコピーを作成するため、変更前のイメージはデータ ブロック自体に存在します。これにはいくつかの利点があります (コミットとロールバックはどちらも非常に単純な操作であり、管理する追加のスペースはありません)。 (これらの古い行バージョンは、バックグラウンドでバキュームする必要があります)。
PostgreSQL の場合 (これは私が内部構造に最も精通している DB です)、ディスク上の各行バージョンには、その行バージョンが「可視」であるかどうかを判断するためにトランザクションが調べなければならない追加のプロパティがあります。簡単にするために、"xmin" と "xmax" があるとします。"xmin" は行バージョンを作成したトランザクション ID を指定し、"xmax" はそれを削除した (オプションの) トランザクション ID を指定します (新しい行バージョンを作成して行の更新を表します)。したがって、txn#20 によって作成された行から開始します。
xmin xmax id value
20 - 1 FOO
そして txn#25 が実行されますupdate t set value = 'BAR' where id = 1
20 25 1 FOO
25 - 1 BAR
txn#25 が終了するまで、新しいトランザクションはその変更が表示されないと見なすことを認識します。したがって、このテーブルをスキャンするトランザクションは「FOO」バージョンを取得します。これは、その xmax が目に見えないトランザクションであるためです。
txn#25 がロールバックされた場合、新しいトランザクションはすぐにそれをスキップしませんが、txn#25 がコミットまたはロールバックされたかどうかを考慮します。(PostgreSQL は、これを提供するために「コミット ステータス」ルックアップ テーブルを管理します。pg_clog
) txn#25 がロールバックされたため、その変更は表示されないため、再び「FOO」バージョンが取得されます。(そして、xmin トランザクションが表示されないため、"BAR" バージョンはスキップされます)
txn#25 がコミットされた場合、xmax トランザクションが表示される (つまり、そのトランザクションによって行われた変更が表示される) ため、"FOO" 行バージョンは取得されません。対照的に、xmin トランザクションが表示されている (xmax がない) ため、"BAR" 行バージョンが使用されます。
txn#25 がまだ進行中 (これも から読み取ることができます) の間、行を更新したい他のトランザクションは、トランザクション IDpg_clog
で共有ロックを取得しようとすることによって、txn#25 が完了するまで待機します。私はこの点を強調しています。これが、PostgreSQL が通常「行ロック」自体を持たず、トランザクション ロックのみを持たない理由です。変更された各行に対するメモリ内ロックはありません。(使用するロックは、xmax を設定することによって行われ、xmax を示すフラグは削除ではなくロックを示すだけです)select ... for update
オラクル...多少似たようなことをしますが、詳細に関する私の知識ははるかに曖昧です。Oracle では、各トランザクションにシステム変更番号が発行され、それが各ブロックの先頭に記録されます。ブロックが変更されると、その元の内容が元に戻すスペースに置かれ、新しいブロックが古いブロックを指すようになります。したがって、基本的に、ブロック N のバージョンのリンクされたリスト (データ ファイル内の最新バージョン) があり、徐々に古いバージョンになります。 UNDO 表領域で。ブロックの上部には、ロックを何らかの方法で実装する「関心のあるトランザクション」のリストがあります (変更された行ごとにメモリ内ロックがありません)。それ以上の詳細は思い出せません。
私が信じている SQL Server のスナップショット分離メカニズムは、専用ファイルではなく、tempdb を使用して変更中のブロックを格納するという点で、Oracle のものとほぼ同じです。
このとりとめのない答えがお役に立てば幸いです。それはすべてメモリからのものであるため、特にpostgresql以外の実装では、大量の誤報が発生する可能性があります.