PREPARATION
受験対策

OSS-DB入門

第7回 トランザクション

第5回、6回を通して、SQLの使い方には慣れてきたでしょうか。今までは、基本的に単一のSQLを実行してその結果を確認するということを行ってきましたが、実際の業務フローの中では、単一のSQLのみで完結するようなケースだけに限らず、複数のSQLを連続して発行する場面も多々存在します。今回は、このような複数のSQLを取り扱う上で重要となる「トランザクション」の概念について学習していきましょう。

トランザクションとは

今回のテーマである「トランザクション」とは一体何のことでしょうか?「オススメ!OSS-DB情報」の第1回でも取り上げられているのでご存じの方も多いことでしょう。(記憶の曖昧な方はぜひ読み返してみてください)
そちらの記事にも出ているように、トランザクションとは複数のSQL文を一連のものとして扱う処理単位を意味します。
そしてこのトランザクションの重要な点は、一連のSQLが「すべて完全に実行される」か「まったく実行されない」かのどちらかしかなく、一部分だけが実行されるような中途半端な状態は許されないことにあります。

複数のSQL実行を処理単位とするようなケースは、実際の業務フローの中でもしばしば登場します。

例えば、AさんからBさんへ10万円を送金する場合を考えてみましょう。処理は次のような流れで行われるものとします。

Aさんの口座から10万円を引く

Bさんの口座へ10万円を加える

1.が実行された直後に何らかの理由で処理が中断されたとしたらどうなるでしょうか。10万円がまるまる消失してしまうことになりますね。このような事態を避けるためにも、1.と2.はセットで扱わなければいけないことが理解できるかと思います。

PostgreSQLでは、トランザクションを定義するコマンドとしてBEGIN/COMMITを使用します。BEGINコマンドを実行すると、トランザクションが開始したとみなされ、それ以降に実行されるSQLは、COMMITが実行されるまでが一連のトランザクションとして扱われます。仮に途中でエラーが起きたとしても、BEGIN以降の処理はすべて無効化されますので、データの不整合を回避することができます。
※エラー発生時以外にも、ROLLBACKコマンドを実行することで明示的にトランザクションを無効化することも可能です。

先述の例で言えば、次のようにすることでひとつのトランザクションとして実行できるようになります。

BEGIN;
  Aさんの口座から10万円を引く
  Bさんの口座へ10万円を加える
COMMIT;

トランザクション分離レベル

PostgreSQLはマルチプロセスで処理が行われますので、当然ながら複数のトランザクションが同時に実行されることになります。トランザクション同士が相互に影響することにより、次のような不整合を生じる可能性があります。

ダーティリード

他のトランザクションが更新した未コミット状態のデータを読み込んでしまうこと。

反復不能読み取り

トランザクション内で同じレコードを2回読み込んだとき、読み込み間隔内に他のトランザクションが該当レコードの更新をコミットしたことで、1回目と2回目の読み込み結果が変わってしまうこと。

ファントムリード

トランザクション内で同一条件のレコード抽出を2回行ったとき、読み込み間隔内に他のトランザクションがレコードの挿入/削除をコミットしたことで、1回目と2回目の結果のレコード数が変わってしまうこと。

このようなトランザクション同士の干渉を防ぐためには、個々のトランザクションを分離する必要があります。
SQL標準規格ではトランザクションの分離レベルを以下の4段階で定義しています。分離性は下に行くほど高くなります。

READ UNCOMMITTED

未コミット状態のデータであっても読み込む。

READ COMMITTED

コミットされたデータのみを読み込む。

REPEATABLE READ

トランザクション中は他のトランザクションでコミットされた更新を参照しないことで、同じレコードを繰り返し読んでも常に同じ結果が得られることを保証する。

SERIALIZABLE

トランザクションの逐次実行をエミュレートし、直列的に実行した場合と同じ結果になることを保証する。

トランザクションの分離レベルと、それによって制御できる不整合との関係はオンラインマニュアルなどでまとめられていますのでご覧になってください。
http://www.postgresql.jp/document/9.1/html/transaction-iso.html

PostgreSQLはいずれの分離レベルでも設定できるようになっています(ただし、'READ UNCOMMITTED'に設定しても内部的には'READ COMMITTED'と同じ動作となり、ダーティーリードという望ましくない結果が起きないようになっています)。分離レベルの設定は、SET TRANSACTIONコマンド、あるいはpostgresql.confのdefault_transaction_isolationなどで行うことが可能です。

演習

文章だけではなかなかイメージも湧かないでしょうから、実際にSQLを実行して、トランザクション分離レベルを体感してみましょう。前回までと同じようにVirtualBoxで仮想マシンを起動したら、端末を立ち上げてください。

まず最初に、現在のトランザクション分離レベルを確認しておきましょう。SHOWコマンドで見ることができます。

SHOW TRANSACTION ISOLATION LEVEL;
分離レベルは'READ COMMITTED'(これがPostgreSQLのデフォルト値です)であることが分かります。
ここで、もうひとつ端末を立ち上げ、お互いのトランザクションがどのように干渉するのかを見てみます。

2つの端末から、特定のレコードに対して次のような順番でコマンドを実行してみてください。

端末A 端末B
--------- --------
1: BEGIN BEGIN
2: SELECT
3: UPDATE
4: SELECT
5: COMMIT
6: SELECT
ステップ4および6のSELECT結果はどのようになるでしょうか。
分離レベルが'READ COMMITTED'の場合、ダーティーリードは防止できますが、反復不能読み取りの発生を防ぐことはできません。ということは・・・?
実際に実行してみた結果を示します。
ステップ4の段階では陸奥太郎さんのレコードは未コミット状態ですので、端末AでUPDATEした内容(tel列を'0172-26-8888'→'0172-26-1234'に変更)は端末Bからは見えていません。ところが、ステップ6になると、端末Bはコミット後のレコードを読み込んでいますので、同一トランザクション内にありながら、直前に行ったSELECTの結果とは異なる値になってしまいました。
発生した現象をまとめると次のようになります。

端末A 端末B
--------- --------
1: BEGIN BEGIN
2: SELECT
3: UPDATE
4: SELECT <--ダーティーリード発生しない
5: COMMIT
6: SELECT <--反復不能読み取り発生
'READ COMMITTED'の設定では反復不能読み取りが起こりうるという事実を実感できたかと思います。
他にも、ステップ3のタイミングで端末AからINSERT/DELETEを発行してファントムリードが発生することを確認してみたり、SETコマンドで分離レベルを'REPEATABLE READ'または'SERIALIZABLE'に変更してみて端末BのSELECT結果がどのように変わってくるのかを見てみたりしますと更に理解が深まることでしょう。

最後に

トランザクションの基本的な概念はつかめたでしょうか。
トランザクションの分離レベルに関しては、一概にどのレベルが最適と言うことはできません。トランザクションをまったく管理することなくそれぞれを並行して実行させると、処理パフォーマンスは向上しますが、上で挙げたような様々な不整合が発生するリスクも高まります。逆に、個々のトランザクションが干渉しないように順番よく実行するようにすると、データの整合性は保証されるものの、処理パフォーマンスの低下を招く可能性があります。同時実行性と分離性とはトレードオフの関係にありますので、バランスを考えて設定する必要があります。
機能的な面に目を向けますと、トランザクションの分離を実現するにはロックと呼ばれるメカニズムが使用されています。ロックと言うと、行レベルロック/テーブルレベルロックの使い分けや、「オススメ!OSS-DB情報 第12回」で取り上げているデッドロックの対策方法など、データベースを扱う上では理解しておきたいトピックが多々あります。興味のある方はこちらをご参照ください。

PostgreSQL日本語ドキュメント 第 13章同時実行制御
http://www.postgresql.jp/document/9.1/html/explicit-locking.html

オススメ!OSS-DB情報  第12回 デッドロックについて
http://www.oss-db.jp/measures/dojo_12.shtml

執筆:
平敷 兼貴 氏(OSS-DB Gold認定者)株式会社オークニー ジオソリューション事業部
FOSS4G(オープンソース地理空間ソフトウェア)を活用したアプリケーションやサービスの設計・開発に従事。
当連載の執筆を契機にPostgreSQLを再勉強し、2013年初めにOSS-DB Goldを取得。PostgreSQLマスターを目指して日々研鑽中。

アドバイザー:
森 亮 氏 株式会社オークニー代表取締役
2002年にオークニーを設立し、代表取締役に就任。マッピング・ GIS・LBS・ITS関連プロジェクトのビジネスコンサルティングを経て、現在は会社経営の傍ら、『入門Webマッピング』(Tyler Mitchell原著;オライリー・ジャパン発行)の翻訳、Open Source Geospatial財団日本支部の代表者を務めるなど、FOSS4G(オープンソース地理空間ソフトウェア)の普及活動に取り組んでいる。

監修:
松田 神一

LPI-Japan
Platinum Sponsors

関連資格