ニュース・ブログ

ニュース・ブログ一覧PHPの記事情報CakePHPでDBトランザクションを入れ子にした場合の動作検証

CakePHPでDBトランザクションを入れ子にした場合の動作検証

トランザクション
 
おチャクラ全開こんにちは、SGKです。
今回は CakePHP でDBトランザクションを入れ子で組んだ場合にどのように動作するか検証してみたので、その結果を発表したいと思います。

 


 

DBトランザクションとは

改めて説明すると、データベースに対する削除や更新などの変更を実行はするが保留にしておいて、
問題があった場合に巻き戻すために使用する機能です。
主な使い方としては関連性を持って保存される2つ以上のデータの更新で問題があった場合に巻き戻す場合などに有用です。

よく見るのが以下のような try – catch を絡めたトランザクション処理です。
※関連性の定義がしっかりしてあればこんなことしなくて良いんだけど、そこは例なので、、

try{
	//トランザクション開始、begin 以降のデータへの更新は保留される
	$connection = \Cake\Datasource\ConnectionManager::get( 'default' );
	$connection->begin();

	//ユーザーの登録
	$tblUsers = \Cake\ORM\TableRegistry::getTableLocator()->get( 'Users' );
	$entUser = $tblUsers->newEntity([ 'name'=>$name ]);
	$tblUsers->saveOrFail( $entUser );

	//ユーザーのアイテムを一緒に登録
	$tblItems = \Cake\ORM\TableRegistry::getTableLocator()->get( 'Items' );
	foreach($inputItems as $inputItem){
		$entItem = $tblItems->newEntity([
			'user_id' => $entUser->id,
			'name' => $inputItem['name'],
		]);
		$tblItems->saveOrFail( $entItem );
	}

	//成功、保留した更新を実行
	$connection->commit();

}catch( \Exception $e ){
	//失敗、begin を実行した後の更新を巻き戻す
	$connection->rollback();
}

こうしておけばItemsテーブルに対する登録で何かエラーが発生しても Users と Items に対して行ったレコードの追加処理の部分、begin() を実行して rollback() を実行するまでに行われたDBへの変更はすべてキャンセルする事が出来ます。

本題

さて、ひととおりトランザクションの説明をしたので本題です。
このトランザクション処理を入れ子状態で行った場合、どのように動作するのか、今まであったようで無いようなボンヤリ理解しているフリしている部分を、いろんなパターンのコードを実行して明らかにしておこうと思います。

 


 

パターン1

まずはパターン1ですが、その前に入れ子の説明、
以下の様に $con1->begin()、$con2->begin() のように複数のトランザクションに挟まれた更新処理があった場合、それぞれに rollback、commit を行なった場合、データがどうなるかを見ていきます。
※実際のケースでは以下のようなコードはありえませんが、 インデントされた $con2 の部分が別のメソッドになっている事は稀にある事だと思います。

$tbl = \Cake\ORM\TableRegistry::getTableLocator()->get( 'Users' );
$ent = $tbl->get(1);
echo $ent->name; // → 「お名前君」とDBに登録されているとします。

//パターン1
$con1 = \Cake\Datasource\ConnectionManager::get('default');
$con1->begin();

$ent->name = '変更さん1';
$tbl->save($ent);

	$con2 = \Cake\Datasource\ConnectionManager::get('default');
	$con2->begin();

	$ent->name = '変更さん2';
	$tbl->save($ent);

	$con2->rollback();

$con1->commit();
echo $ent->name;

→ この場合、最後の echo は「変更さん2」と表示されるが、実際のデータは「お名前君」のままです。
 
データの状態は理解したとして、この時点で気づかされるのが、データは巻き戻るが Entity の内容は巻き戻らないという点です。
これは入れ子にしなくても rollback した際のハマリポイントなのでついでに書き留めておきます。注意です。

 


 

パターン2

$con1 と $con2 の rollback、commit を入れ替えました。
※以降1からの差分、主要なコード部分のみ書き出していきます。

//パターン2
$con1 = \Cake\Datasource\ConnectionManager::get('default');
$con1->begin();

$ent->name = '変更さん1';
$tbl->save($ent);

	$con2 = \Cake\Datasource\ConnectionManager::get('default');
	$con2->begin();

	$ent->name = '変更さん2';
	$tbl->save($ent);

	$con2->commit();

$con1->rollback();

→ パターン1と変わらずデータは「お名前君」のまま

 


 

パターン3

$con2 の commit の後に更新処理を置き、rollback しています。

//パターン3
$con1 = \Cake\Datasource\ConnectionManager::get('default');
$con1->begin();

	$con2 = \Cake\Datasource\ConnectionManager::get('default');
	$con2->begin();

	$ent->name = '変更さん1';
	$tbl->save($ent);

	$con2->commit();

$ent->name = '変更さん2';
$tbl->save($ent);

$con1->rollback();

→ パターン1と変わらずデータは「お名前君」のまま

 


 

パターン4

$con2 の rollback の後に更新処理を置き、commit しています。

//パターン4
$con1 = \Cake\Datasource\ConnectionManager::get('default');
$con1->begin();

	$con2 = \Cake\Datasource\ConnectionManager::get('default');
	$con2->begin();

	$ent->name = '変更さん1';
	$tbl->save($ent);

	$con2->rollback();

$ent->name = '変更さん2';
$tbl->save($ent);

$con1->commit();

→ データは「変更さん2」に変更されました。

 


 

パターン5

で、もしかして、と思って実験してみたのが パターン5 です。
入れ子にせずに パターン4 と同じように 更新 → 巻き戻し → 更新 → コミット をしてみました。

//パターン5
$con1 = \Cake\Datasource\ConnectionManager::get('default');
$con1->begin();

$ent->name = '変更さん1';
$tbl->save($ent);

$con1->rollback();

$ent->name = '変更さん2';
$tbl->save($ent);

$con1->commit();

→ もちろん パターン4 と同じように「変更さん2」に変更されました。

 


 

総括

ん~なんか難しく考えていましたが、ようするに入れ子にしなかった場合と同じ!
rollback したらそれまでの更新作業はクリアされ、再び更新作業を行なってコミットされればその部分は更新される。
という至極当たり前の結果でした。。

 

以上、何かの参考になれば、と思います。
DBトランザクションを入れ子にした場合の動作についてでした。