アプリ版:「スタンプのみでお礼する」機能のリリースについて

このカテゴリには初めて投稿いたします。
どうにも解決ができないため、質問させていただきました。

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も禁止)にする方法はどうやったらよいのでしょうか?」
ということでございます。

ご存じの先生方、是非、お力お貸しいただけますでしょうか。
何卒よろしくお願いいたします。

A 回答 (2件)

トランザクション処理は他からの更新をロックする機能ではないから。

。。
    • good
    • 0

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;
    • good
    • 0
この回答へのお礼

mpro-gram様

詳細のご指南ありがとうございました。
いただいた情報にて、全て解決することができました。
すべては、mpro-gram様のご回答のおかげでございます。

検証で1000回繰り返し×3ブラウザで同時 で合計3000回処理させても1件も重複しなくなりました!
(別途、Model->queryで検証した際に2回目以降の同じクエリだとキャッシュが有効になることを知らなかったため、少し嵌ってしまいましたが、それも方法が分かり解消できました。)

cakephpの標準コードだけでは、for updateの完全な排他制御はできないのですね。

本当に感謝申し上げます。

お礼日時:2014/07/27 09:56

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!