トランザクションは全てのデータベースシステムで基礎となる概念です。 トランザクションの基本的要点は複数の手順を単一の「全てか無しか」の操作にまとめ上げることです。 手順の進行途中の状態は他の同時実行中のトランザクションからは見えません。 トランザクションの完結の障害となる何らかのエラーが起こると、それらの手順はどれもデータベースにまったく影響を与えません。
例を挙げましょう。ある銀行のデータベースでそこに多数の顧客の口座残高と支店の総預金残高が記録されているとします。 アリスの口座からボブの口座に$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がボブに振り込まれないでアリスの口座から引き落とされたとしたら、アリスはこの銀行のお得意様ではなくなるでしょうね。 操作の途中で一部不都合が発生した場合、結果に影響を与えるいかなる手続きも実行されないという確証が必要です。 更新手続きをトランザクションにグループ化すると、その確証が得られます。 あるトランザクションは他のトランザクションから見て完結するかまったく起こらなかったかという見方から原子的と呼ばれます。
もう一方で、いったんトランザクションが完結しデータベースシステムに承認された場合は、確実に恒久的に保存され、たとえ直後にクラッシュが起こったとしても記録は失われないという確証も必要です。 例えばボブが自分の口座から現金を引き落として店舗を立ち去った直後にボブの口座からの引き落とし記録がシステムのクラッシュで消えてしまうことは受け入れられません。 トランザクションが実装されているデータベースでは、あるトランザクションによる全ての更新がそのトランザクションを完結したと通知を行う前に永続的記録装置(すなわちディスク上)にログを書き込むことで保証しています。
トランザクションを実装したデータベースの別の重要な特性は、原子的更新という概念に深く関係しています。 複数のトランザクションが同時に動作している時、それぞれのトランザクションは別のトランザクションが行っている未完了の変更を見ることができてはなりません。 例えば、あるトランザクションがすべての支店の残高を集計する作業を行っているとき、アリスの口座がある支店からの引き落としを勘定に入れるけれども、ボブの口座がある支店への振り込みを勘定に入れないというのは受け入れられませんし、その逆も駄目です。 つまり、データベース上での恒久的効果という意味のみならず、一連の操作の過程で可視性ということにおいてもトランザクションは「すべて」か「なし」かでなければなりません。 作業中のトランザクションによる更新は、他のトランザクションからはトランザクションが完結するまで不可視です。 そのトランザクションが完結したその時点で、トランザクションで行った更新の全てが見えるようになります。
PostgreSQLではトランザクションを構成するSQLコマンドをBEGIN
とCOMMIT
で囲んで設定します。
従って、この銀行取引のトランザクションの実際は次のようになります。
BEGIN; UPDATE accounts SET balance = balance - 100.00 WHERE name = 'Alice'; -- 等々 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
コマンドがトランザクションブロックの制御を取り戻す唯一の手段です。