- 2008-06-07 (土) 0:06
- CakePHP
@deprecated
この情報はCakePHP1.2RC1までのものです。2008/06/26現在のリポジトリではチケットが反映され修正が完了しています。
CakePHP+PostgreSQLでModel#getInsertID()を使う場合、別セッションのシーケンス値が取得される問題があります。
問題点
ユーザ情報とその付加情報や、注文情報と明細情報などINSERT後にそのレコードに関連する情報としてid値を活用する場合、本来とは異なるレコードに結びつく可能性があります。
ex) 別の注文情報に明細が登録される等
ただ、これはほぼ同時にINSERT文が発行された際に起こる現象ですので、それほど登録処理が行われないサイトではあまり遭遇するものではありません。(ですので、これまであまり表面化しなかったかと。)
CakePHP1.2RC1/1.2-beta/1.1.19でこの問題があります。
解決策
フレームワークを修正して貰えるようにチケットは投げています。
https://trac.cakephp.org/ticket/4832
現状のソースのままで対応するなら、INSERT前にLOCK TABLEで該当テーブルをロックしておくことで対応できます。(nextval()で直にシーケンス値を変えられるとNGですが。)
<?php // Order は Model $this->Order->begin(); $this->Order->query('LOCK TABLE orders IN EXCLUSIVE MODE'); if (!$this->Order->save($data)) { $this->Order->rollback(); } else { $this->Order->commit(); } ?>
概要
PostgreSQLでは、自分が発行したINSERT文に対するシーケンス値を取得する際はcurrval()というPostgreSQLの関数を使用します。この関数は自セッションで発行された最新のシーケンス値が返ってくるので、別セッションでINSERT文が発行されても値は変わりません。これにより自分がINSERT文で追加したレコードを特定できるわけです。
SELECT currval('hoge_id_seq');
以前はCakeでもこのcurrval()を使って実装されていたわけですが、いつの頃か実装が変わって、現在のlast_valueを取得する形に変わっています。
SELECT last_value FROM hoge_id_seq;
last_valueはシーケン自体の最新の値になるので、上記のように別セッションにてINSERT文が実行されると、そちらで割り当てられたシーケン値が返ってきます。
流れとしては下のようになります。(初期シーケンス値が0、A/Bが同時に登録したと仮定)
[SQL]
CREATE TABLE hoge(
id serial primary key
,name text
);
[/SQL]
[SQL]
A: INSERT INTO hoge(name) value(‘foo’); // id=1
B: INSERT INTO hoge(name) value(‘bar’); // id=2
A: SELECT last_value FROM hoge_id_seq; // 2!
B: SELECT last_value FROM hoge_id_seq; // 2
[/SQL]
この場合、どちらも2がModel#getInsertID()の値として返ってきます。
当然この値をhoge_idとして別のテーブルに登録すれば、本来hoge_id=1に登録されるべきであったレコードがhoge_id=2として登録されてしまうわけです。
頻度は少ないが、発生すると問題
それほど発生することは無いにせよ、発生した際の影響が大きいです。(注文情報とカード番号が別テーブルになっていて、別の注文情報にカード番号が登録されたら。。。)
フレームワークが修正されるのも大事ですが、LOCK TABLEを活用してトランザクション内で別のINSERTが発生しない(シーケンスが進まない)方がより安全に登録処理を行うことができます。状況が許すなら活用してみて下さい。
- Newer: PHP NULLかどうかはis_null()を使う
- Older: [告知] 第3回CakePHP勉強会が開催されます。
コメント (Close):2
- vector 08-06-07 (土) 23:58
-
はじめまして。
$this->Order->begin()/rollback()/commit()
ですが、Modelにトランザクションのメソッドは実装されていませんよね?
svnで最新を追ってるつもりですが、Behaviorで実現していますので。Controller内で行うならば
$db =& ConnectionManager::getDataSource(‘default’);
$db->begin()/rollback()/commit();
ではないでしょうか。 - shinbara 08-06-08 (日) 10:34
-
情報ありがとうございますー:-D
ご指摘のとおりModelにはbegin()/rolback()/commit()は実装されていません。
ただModelに存在しないメソッドは、最終的にはDboSource#query()からメソッド名がそのままSQLとして実行されます。本文の例なら
begin;
commit;
rollback;
がそのままSQLとして実行されるわけです。
@see http://www.1×1.jp/blog/2007/06/cakephp_sql.htmlもちろんDboSource#(begin|rollback|commit)\(\)を実行してトランザクション管理をフレームワークに任せるならvectorさんが書かれている手順が必要です。
本文は手抜き手順ですね;-)
トラックバック:1
- このエントリーのトラックバックURL
- /blog/2008/06/cakephp_dbo_postgres_currval.html/trackback
- Listed below are links to weblogs that reference
- CakePHP PostgreSQLではgetInsertID()に注意 from Shin x blog
- pingback from postgresql シーケンス取得方法(currvalのlast_valueの違い) | 携帯サイトをつくろう。 09-04-17 (金) 20:06
-
[…] Shin x blog – CakePHP PostgreSQLではgetInsertID()に注意 […]