sqlが思い通りの挙動にならない


ページを見るとカウントをとるプログラムを書きました。
※idをユニークに設定。mysql使用。

閲覧やリロードでidのレコードデータがない場合は、レコード(numberは1)を挿入。
既にidがある場合は、numberに+1してカウントしていくものです。

$request_data=$pdo->
prepare("
INSERT INTO table
(id,number)
VALUES
(:id,1)
ON DUPLICATE KEY UPDATE
number = number+1
");

$request_data->bindValue(":id",$id);
$request_data->execute();


上記のプログラムだと、挿入や更新自体は問題がありません。
しかし、idの無いデータで挿入を行ったにも関わらず、numberの値が2となったり、更新時に+2されることがあります。
※うまくいく時とうまくいかない時があります。閲覧やリロードは1回のみ。

また、データベースをみていると後から遅れてデータが入ってくる時もあります。
データベースの遅延?トラブル?とも考えられますが、解決法として何が考えられるでしょうか?

回答の条件
  • 1人5回まで
  • 登録:
  • 終了:2014/08/20 10:44:34
※ 有料アンケート・ポイント付き質問機能は2023年2月28日に終了しました。

回答2件)

id:pogpi No.1

回答回数428ベストアンサー獲得回数59

numberをSELECTして、なければINSERT、あれば+1してUPDATEした方がいいと思います。

id:jamis

回答ありがとうございます。

2014/08/16 21:26:23
id:a-kuma3 No.2

回答回数4974ベストアンサー獲得回数2154

ポイント100pt

そのプログラムは、自分一人で使ってる、という状況でしょうか。
例えば、テスト中だから公開してない、とか、ローカルな環境で動いてる、とか。

本当に自分の目の前で動かしているものだけなのかどうかは、クエリログを取ってみるのが確実です。
http://server-setting.info/centos/mysql-log-type.html

一人で使っているということが確実ならば、気になるのは commit です。
「DB に、遅れて書き込まれている」というのが引っ掛かります。
質問の文面のコードには書かれていませんが、PDO::commit は、きちんと呼び出しているでしょうか?

id:jamis

回答ありがとうございます。

公開はしていますが、テスト中なので他のアクセスはない状態です。
他の競合がいるわけではないのでトランザクションは設定していませんでした。
可能性はありますね。試してみます。

2014/08/16 21:19:04
id:jamis

//トランザクション開始
$pdo->beginTransaction();

$request_data=$pdo->~~

//commit
$pdo->commit();

としてみましたが、勝手に数値が増加しています。
これはこの部分だけで無く、他のプログラムが干渉している様な全体として何か間違っている可能性が高そうです、、。

2014/08/19 19:07:43
id:jamis

質問者から

jamis2014/08/25 21:22:45

結局、何を設定しても問題点は解決できませんでした。

対応策として、ページが読み込まれた後にajaxでデータを飛ばして、別ページのphpファイルで処理するようにしてカウントするようにしました。

この方法だと問題が出なかったので、何かページ内部のプログラムが干渉していたか、ブラウザの挙動に関してつかみ切れていなかったのかもしれません。

真相が掴めなかったのは残念でしたが、みなさんご協力ありがとうございました。



08/25 追記

いろいろと検証をしてみました。

結果、ひとつのページにいくつかのクエリがある中、あるクエリで著しく遅くなっているものを見つけました。2秒程度。

※近い位置にソーシャルボタンを設置していたので、ページの読み込みが遅いのはソーシャルボタンが要因であると思い込んでいました。

どうも原因はそれだったようで、そのクエリを改善した結果、問題の複数カウントなどの現象はなくなりました。

PHPの挙動についてはっきりと認識しているわけではありませんが、ひとつのクエリが遅い場合、他のクエリが影響を受けて複数実行してしまう事もあるようです。※予測であり未確認

ajaxにすると適正値が挿入されたのは、jqueryを使用していたためページが読み込まれた後に実行されたせいかもしれません。

  • id:language_and_engineering
    table のDDLが知りたいですね。CREATE文を貼り付けていただけると助かりますが。
    あと,トランザクションレベルは変えていますか?
    (numberをSELECTしてプログラム側でif文で分岐,だと,リロード時にレコードがダブってしまう)
  • id:jamis
    ありがとうございます。

    phpmyadminで簡単につくったものでidにユニークを設定しただけのものです。id,numberともにint(11)。
    トランザクションは現状、一切使用していません。これから試してみようと思います。

    id|number
    ----------
    1|1
    2|6
  • id:language_and_engineering
    こんにちは。

    どうしてこの件がエラーになるのかな…と考えてましたが,
    やはりプログラム中の$idの新規発行部分を見ないとわからないと思います。
    下記のような流れが発生した場合,「排他処理」を行なっていないのでうまくいきません。

    ユーザがアクセス

    サーバは新規IDを発行・・・(1)

    ユーザが素早くリロードボタンを押す・・・(2)

    サーバは(1)で発行したIDをDBに登録。新規レコードとして処理

    サーバは(2)に対し新規IDを発行・・・(3)

    サーバは(3)で発行したIDをDBに登録。新規レコードとして処理

    同一ユーザのアクセスが別IDで2件保管されてしまった

    詳しくは,Webアプリにおけるセッションの実装と,排他処理を調べてみてください。
  • id:jamis
    ありがとうございます。
    参考に試してみます。

この質問への反応(ブックマークコメント)

「あの人に答えてほしい」「この質問はあの人が答えられそう」というときに、回答リクエストを送ってみてましょう。

これ以上回答リクエストを送信することはできません。制限について

回答リクエストを送信したユーザーはいません