
このカテゴリには初めて投稿いたします。
どうにも解決ができないため、質問させていただきました。
cakephpで、注文番号の連番発行のために、今回初めてトランザクション処理が必要なケースが出てきまして、
ネットで検索して出て来る情報を元に、そのとおりに記述しているのですが、どうやってもうまく行きません。
DBのテーブルはInnoDBになっています。
参考にしたサイトは例えば
http://wataame.sumomo.ne.jp/archives/3812
などです。
--------------------実際のコード
-----以下はあるモデル内(ここでは、SamplemodelDataとしています)に記述した関数での処理です。$thisはそのモデルを示します。
$dataSource = $this->getDataSource();
$dataSource->begin($this);
$res = $this->find('first',array('conditions'=>array('classification'=>$classification,'commoncode'=>$commoncode)));
if($this->getNumRows()==0){//レコードなしの場合
$returnNumber = 1;
$this->save(array('classification'=>$classification, 'commoncode'=>$commoncode, 'number'=>1));
}else{
$res['ExsamplemodelData']['number'] += 1;
$returnNumber = $res['SamplemodelData']['number'];
$this->set('id', $res['SamplemodelData']['id']);
$this->saveField('number', $returnNumber);
}
$dataSource->commit($this);
return $returnNumber;
--------------------
■目的
ユーザーが同時刻に何人同時に注文しようと、注文番号を重複させずに注文番号を採番することが目的です。
極端なことを言えば、ある同じ時刻(秒まで一緒)に世界各国から100人同時に全く同じタイミングで注文が入っても、注文番号を重複させないようにしたいです。
※DBのシリアルを使えば確実に重複させないようにできることは知っています。しかし、今回は単純に連番だけでなくいろいろなケースにおいて意味をもつ文字列も付与したものをプライマリキーとしているので、単純なシリアルではだめなのです。
■うまくいかない点
・上記の記述でも、トランザクション自体は機能しているようです。
最後の $dataSource->commit($this); をコメントアウトにすると、DBの番号が永遠にインクリメントされませんので。
・begin ~ commit までの間に、他のスレッドで、
$this->find('first',array('conditions'=>array('classification'=>$classification,'commoncode'=>$commoncode)));
が実行されると、インクリメントされる前の番号が返されるのです。
=>それよりも前のスレッドが begin をした瞬間からcommitするまでは、他のスレッドでfindしても、待ち状態になって欲しいのです。
・検証用プログラムで、上記の処理を、2つのブラウザから同時に100回繰り返す(2つ併せて200回繰り返し処理させる)と、
毎回200件中、5~7件程度、番号が重複してしまいます。
■質問内容
・このような精度を求めるようなケースでは、cakephpでトランザクション処理をしても、もともと無理な要望なのでしょうか?
・上記の記述で不足している部分は何でしょうか?例えば、mysqlのトランザクションには他スレッドから、updateだけを禁止にする指定と、updateとselectも禁止にする指定ができるようですが、上記の記述だと他スレッドではupdateしか禁止されていないために、selectであるfindは待ちが発生しないということなのでしょうか?しかし、selectも禁止にするとかそういう指定方法がどう探してもそういう情報が見つけられませんでした。
要約すると、つまり、
「cakephpでトランザクション処理(beginからcommitの間は、他スレッドからはupdateもselectも禁止)にする方法はどうやったらよいのでしょうか?」
ということでございます。
ご存じの先生方、是非、お力お貸しいただけますでしょうか。
何卒よろしくお願いいたします。
No.2ベストアンサー
- 回答日時:
find('first', のとき、降順設定しないと、取得する先頭1行目は最小値だけど、そこは設定してますか?
ちゃんと最大値を得るようになっていたとして、あとは、cakePHPよりは、MySQL innodb の問題じゃないかな。
http://dev.mysql.com/doc/refman/5.1/ja/innodb-lo …
innodb のtransaction は、defaultは REPEATABLE READ で、select 文でのデータ取得時は、他者に読み書き可能なので、これを防ぐには、先に発行される select文に for update が必要です。これで、他者の読み書きをブロックします。
Modelの findメソッドでは、それを追加するすべはないので(接続先データベースによって対応の違う物は実装されていない)
よって、 Model->query(string $sql ,array $placedata) メソッドで直接SQL文を渡して実行することになります。
プレースホルダーも使えるので、pdoでのプレースホルダー指定方法に則って作成するとよいです。
SQL例
SELECT max( number ) as number FROM exsample_models where classification=? and commoncode=? FOR UPDATE;
mpro-gram様
詳細のご指南ありがとうございました。
いただいた情報にて、全て解決することができました。
すべては、mpro-gram様のご回答のおかげでございます。
検証で1000回繰り返し×3ブラウザで同時 で合計3000回処理させても1件も重複しなくなりました!
(別途、Model->queryで検証した際に2回目以降の同じクエリだとキャッシュが有効になることを知らなかったため、少し嵌ってしまいましたが、それも方法が分かり解消できました。)
cakephpの標準コードだけでは、for updateの完全な排他制御はできないのですね。
本当に感謝申し上げます。
お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!
関連するカテゴリからQ&Aを探す
おすすめ情報
デイリーランキングこのカテゴリの人気デイリーQ&Aランキング
-
phpのheader("Location:#pos")...
-
フォームで戻った際に入力済み...
-
SplFileObject を利用したとき...
-
PHP8を使うと、大量のWarningが...
-
セッション関数を使わずにファ...
-
composerをインストールしたい...
-
php 完了画面の送信メールのコ...
-
csvファイルについて教えて下さ...
-
PHPの変わった閉じタグの必要性...
-
PHP8でWarning:Undefined varia...
-
phpの問い合わせフォームを作っ...
-
marginの値でマイナス値を設定...
-
submitで思うようにページが遷...
-
HTML PHP ラジオボタンのイベント
-
php でqiitaのサイトにあったフ...
-
PHPの勉強してます。 配列のと...
-
BASIC認証のフォームをデザイン...
-
アップロードファイルを表示す...
-
複数のパソコンの中の1つのパソ...
-
返信機能のツリー構造の深さを...
マンスリーランキングこのカテゴリの人気マンスリーQ&Aランキング
-
C言語の配列をPush(追加)する...
-
CArrayのソート
-
再帰関数を用いて配列の合計を...
-
行列
-
CArrayの要素としてCStringArra...
-
プログラミングのPythonのnoteb...
-
スカラーのベクトル微分
-
cakephpでのトランザクション処...
-
文字列の抜き出し(PHP)
-
pg_copy_fromの使い方について...
-
【PHP】配列のキー名の修正は可...
-
np.stack()とnp.array()の違い
-
PHPは何故値渡しより参照渡しの...
-
多次元配列をソートする綺麗な...
-
配列の要素(value)に、変数を...
-
タグの中身だけを取り出す正規
-
PHPのmin関数、「1」以上の数値...
-
fgetc関数について
-
テキストエリアに入力した複数...
-
配列の添え字が小数だとどうなる?
おすすめ情報