トランザクション | Database

更新処理が複数連続する場合、それらが論理的に一つの処理であるならば、全部正常なら成功、一つでも異常なら失敗というように扱う必要があります。そのようなときはトランザクション機能を使用すると良いでしょう。トランザクションの開始、終了、破棄のコードで一連の処理を囲んでください。
Sabel_Db_Transaction::activate();    // 開始

try {
  // 更新処理1
  ...
  
  // 更新処理2
  ...
  
  Sabel_Db_Transaction::commit();    // 終了
} catch (Exception $e) {
  Sabel_Db_Transaction::rollback();  // 破棄
  throw $e;
}
上記のコードでは、更新処理2が実行される時には更新処理1が終わっているはずですが、これは一時的なものであり、データベースに完全に反映されている状態ではありません。更新処理2も正常に終了すれば、トランザクションが終了(コミット)され、データベースに完全に反映されます。しかし、更新処理2が失敗(例外が発生)すれば、トランザクションの一時的な状態は破棄(ロールバック)され、更新処理1は無かったこととなります。

トランザクションの開始は Sabel_Db_Transaction::activate() で行いますが、正確にはこの時点で実際にトランザクションの開始がデータベースに通知されるわけではありません。activate() によるトランザクション宣言後、データベースにコネクションを張る際(Sabel_Db_Connection::connect())に通知されます。内部動作が少々複雑ですが、Sabel_Db::createDriver() によりドライバをロードしていれば、トランザクションの開始は適切なタイミングでデータベースに通知されます。sabel.db内部ではドライバは常に Sabel_Db::createDriver() によりロードされているため、通常はこの複雑な内部動作を気にする必要は全くありません。

複数のデータベースが存在するとき

アプリケーションが複数のデータベースを更新することがあるかもしれません。Sabelでは分散トランザクション(XAトランザクション)をサポートしていませんが、簡易的な複数のデータベースにまたがるトランザクションはサポートしています。

トランザクションに関するコーディングは何も変わることはありません。
下記のコードにおいて、更新処理1は db1 に、更新処理2は db2 に走るものとします。
 1: Sabel_Db_Transaction::activate();
 2:
 3: try {
 4:   // 更新処理1(db1)
 5:   ...
 6:   
 7:   // 更新処理2(db2)
 8:   ...
 9:   
10:   Sabel_Db_Transaction::commit();
11: } catch (Exception $e) {
12:   Sabel_Db_Transaction::rollback();
13:   throw $e;
14: }
  1. 1行目でsabel.db内部でトランザクション開始フラグが立つ
  2. 5行目でdb1にトランザクション開始通知が行われ、更新処理が行われる
  3. 8行目でdb2にトランザクション開始通知が行われ、更新処理が行われる
  4. 10行目でdb1がコミットされ、db2がコミットされる
  5. もしくは12行目でdb1がロールバックされ、db2がロールバックされる
注意しなければならないのは、データベースの分散トランザクション機能を使用しているわけではないため、db1 のコミット後の db2 のコミットで異常があったとしても db1 はコミットされたままとなってしまうことです。厳密に分散トランザクションを行いたい場合は、データベース固有の機能を使用すべきです。

隔離レベルの指定

トランザクションの隔離(アイソレーション)レベルを、activate()メソッド の引数で指定することができます。隔離レベルには Sabel_DB_Transaction の4種類のクラス定数を指定することが可能ですが、データベースによって挙動が異なる場合があります。

クラス定数 Oracle
(SQL)
MySQL
PostgreSQL
(SQL)
Firebird
(PHP定数)
Sqlite
READ_UNCOMMITTED READ COMMITTED READ UNCOMMITTED IBASE_WRITE |
IBASE_COMMITTED |
IBASE_REC_VERSION |
IBASE_WAIT
-
READ_COMMITTED READ COMMITTED READ COMMITTED IBASE_WRITE |
IBASE_COMMITTED |
IBASE_REC_NO_VERSION |
IBASE_WAIT
-
REPEATABLE_READ SERIALIZABLE REPEATABLE READ IBASE_WRITE |
IBASE_CONCURRENCY |
IBASE_WAIT
-
SERIALIZABLE SERIALIZABLE SERIALIZABLE IBASE_WRITE |
IBASE_CONSISTENCY |
IBASE_WAIT
-

コード例)
Sabel_Db_Transaction::activate(Sabel_Db_Transaction::SERIALIZABLE);