トランザクションは全てのデータベースシステムで基礎となる概念です。トランザクションの基本的要点は複数の手順を単一の「全てかなしか」の操作にまとめ上げることです。手順の進行途中の状態は他の動いているトランザクションからは見えません。 そして、何らかのエラーが起こるとトランザクションは完結しません。ですからデータベースはエラーの原因となった手順によってまったく影響されることはありません。
例を挙げましょう。ある銀行のデータベースでそこに多数の顧客の口座の残高と支店の総預金残高が記録されているとします。アリスの口座からボブの口座に$100.00の送金があったことを記録したいとします。ちょっと乱暴に単純化すると、このSQLは次のようになります。
UPDATE accounts SET balance = balance - 100.00 WHERE name = 'Alice'; UPDATE branches SET balance = balance - 100.00 WHERE name = (SELECT branch_name FROM accounts WHERE name = 'Alice'); UPDATE accounts SET balance = balance + 100.00 WHERE name = 'Bob'; UPDATE branches SET balance = balance + 100.00 WHERE name = (SELECT branch_name FROM accounts WHERE name = 'Bob');
書かれているSQLコマンドの詳しいことについては今のところ重要でありません。重要な点は、この単純な操作の目的を果たすため、複数の独立した更新手続きが関わっていることです。銀行職員としてはこれら全ての更新が行われるかもしくはまったく行われないのかいずれかの確証が必要です。$100.00がアリスの口座から引き落とされずにボブの口座に振り込まれるようなシステムの不備があってはなりません。一方、$100.00がボブに振り込まれないでアリスの口座から引き落とされたとしたら、アリスはこの銀行のお得意様ではなくなるでしょうね。操作の途中で一部不都合が発生した場合、結果に影響を与えるいかなる手続きも実行されないという確証が必要です。更新手続きをトランザクションにグループ化すると、その確証が得られます。 あるトランザクションは他のトランザクションから見て完結するかまったく起こらなかったかという見方から原子的と呼ばれます。
もう一方で、いったんトランザクションが完結しデータベースシステムに承認された場合は、確実に恒久的に保存され、たとえ直後にクラッシュが起こったとしても記録は失われないという確証も必要です。 例えばボブが自分の口座から現金を引き落として店舗から立ち去った直後にボブの口座からの引き落とし記録がシステムのクラッシュで消えてしまうことは受け入れられません。 トランザクションが実装されているデータベースでは、あるトランザクションによる全ての更新がそのトランザクションを完結したと通知を行う前に永続的記録装置(すなわちディスク上)にログを書き込むことで保証しています。
他にもトランザクション実装のデータベースの重要な特性は、原子的更新という概念に深く関係しています。複数のトランザクションが同時に動作している時、それぞれのトランザクションは別のトランザクションが行っている未完了の変更を見ることができてはなりません。例えば、1つのトランザクションが全ての支店の残高を集計する作業に忙しくて、アリスの口座がある支店がアリスの口座からの引き落としを勘定に入れず、ボブの口座がある支店がボブの口座への振り込みを記帳しないとしたら(その逆もありますが)、どうなるでしょうか。つまり、データベース上での恒久的効果という意味のみならず、一連の操作の過程で可視性ということにおいてもトランザクションは「全て」か「なし」かでなければなりません。作業中のトランザクションによる更新は、他のトランザクションからはトランザクションが完結するまで不可視です。 そのトランザクションが完結したその時点で、トランザクションが行った更新の全てが見えるようになります。
PostgreSQLではトランザクションを構成するSQLコマンドをBEGINとCOMMITで囲んで設定します。そうすると、この銀行取り引きのトランザクションの実際は次のようになります。
BEGIN; UPDATE accounts SET balance = balance - 100.00 WHERE name = 'Alice'; -- etc etc COMMIT;
トランザクション処理の途中でコミットを行わない(アリスの口座残高が足りなかったような場合)と判断した場合は、COMMITではなくROLLBACKを使用して行った全ての更新を破棄します。
PostgreSQLは実際全てのSQL命令文をトランザクション内で実行するようになっています。BEGINを発行しないでも、それぞれの命令文は暗黙的にBEGINが付いているとみなし、(成功すれば)COMMITで囲まれているものとします。BEGINとCOMMITで囲まれた命令文のグループはトランザクションブロックと呼ばれることもあります。
注意: いくつかのクライアントライブラリは自動的にBEGINとCOMMITコマンドを発行し、警告なしにトランザクションブロックが有効になります。使用しているインタフェースのドキュメントで確認してください。
セーブポイントを使用することで、トランザクション内で命令文を、より粒度を細かく制御することが可能になります。セーブポイントは、トランザクションを構成するある部分を選択的に破棄する一方、破棄されない残りの部分をコミットします。SAVEPOINTコマンドでセーブポイントを定義した後、必要であればROLLBACK TOコマンドによりセーブポイントまでロールバックできます。定義されたセーブポイントとロールバックするポイントとの間の全てのトランザクションデータベースの変更は破棄されますが、セーブポイント以前の変更は保持されます。
セーブポイントまでロールバックした後さらにセーブポイントの定義が繰り返されますので、ロールバックのポイント定義は何回でもできます。逆に再度ロールバックする特定のポイントが必要ないのであれば、それを解除しシステムリソースを多少とも解放することができます。セーブポイントを解除したりセーブポイントにロールバックすることは、自動的にその後に定義された全てのセーブポイントを解除することであるということに注意してください。
これら全てはトランザクションブロック内で起こるので、他のデータベースセッションからは何も見えません。トランザクションブロックをコミットした場合、他のセッションからはコミットされた行為が1つの単位として見えるようになりますが、ロールバックの行為は決して可視になりません。
銀行のデータベースを思い出してください。アリスの口座から$100.00を引き出してボブの口座に振り込むとします。後になってボブではなくウィリーの口座に振り込むべきだったと気が付きました。この場合セーブポイントを次のように使います。
BEGIN; UPDATE accounts SET balance = balance - 100.00 WHERE name = 'Alice'; SAVEPOINT my_savepoint; UPDATE accounts SET balance = balance + 100.00 WHERE name = 'Bob'; -- おっと、忘れるところだった。ウィリーの口座を使わなければ。 ROLLBACK TO my_savepoint; UPDATE accounts SET balance = balance + 100.00 WHERE name = 'Wally'; COMMIT;
この例はもちろん極端に単純化していますが、セーブポイントの使用を通じてトランザクションブロックに対し多くの操作を行えることがわかります。さらには何らかのエラーでシステムがトランザクションブロックを中断した場合、ROLLBACK TOコマンドがロールバックを完結させずに再開始させるための制御を取り戻す唯一の手段です。