たとえば、非常に単純な選択ステートメントがあります。
SELECT id, some_more_fields_that_do_not_matter
FROM account
WHERE status = '0'
LIMIT 2
上記は次の ID を返すことに注意してください: 1,2
次に、これらの行を for each でループし、いくつかのレコードを更新します。
UPDATE account
SET connection = $_SESSION['account_id'],
status = '1'
WHERE id = $row_id
これで、ID が 1,2 のテーブル内の行のステータスが「1」になりました (行が正しく更新されていることを確認するためにチェックしています)。それができなかった場合は、すべて元に戻します。すべてがOKになるとすぐに、最初の場所にカウンターがあり、この場合は2であるため、2行が更新されているはずで、これを単純なCOUNT(*)で確認します。この情報は、たとえば次のデータとともに電子メールで送信されます (すべてが正しく更新されたことを意味します)。
- Time of update: 2013-09-30 16:30:02
- Total rows to be updated (selected) = 2
- Total rows successfully updated after completing queries = 2
The following id's should have been updated
(these where returned by the SELECT statement):
1,2
ここまでは順調ですね。奇妙な部分が来ます。ただし、他のユーザーが作成した次のクエリでは、たとえば id の 1,2 が返されることがあります (ただし、ステータス '0' が含まれていないため、これらは SELECT ステートメントによって返されないため、これは不可能です。したがって、何が起こるかはたとえば、次のようなメールを受け取りました。
- Time of update: 2013-09-30 16:30:39
- Total rows to be updated (selected) = 10
- Total rows successfully updated after completing queries = 8
The following id's should have been updated
(these where returned by the SELECT statement):
1,2,3,4,5,6,7,8,9,10
1 と 2 が更新によって選択されるのは本当に奇妙です。ほとんどの場合はうまくいきますが、ごくまれにうまくいかず、ステータス「1」ですでに更新されている同じ ID を返します。
これらの更新の間の時間に注意してください。同じ時間でもありません。最初は、これらのクエリがまったく同時に実行されるようなものだと思っていました (これは不可能ですよね?)。または、これは可能ですか?それともクエリがキャッシュされている可能性がありますか? mysql.conf ファイルでいくつかの設定を編集する必要がありますか?
この問題が発生したことは一度もありません。あらゆる方法で更新を試みましたが、引き続き発生しているようです。これら 2 つのクエリを 1 つの巨大な更新クエリに結合することは、何らかの方法で可能でしょうか? すべてのデータはまったく同じで、奇妙なことは何もしません。これが問題を引き起こす原因と、これがランダムに(まれに)発生する理由について、誰かが手掛かりを持っていることを願っています.
編集:
スクリプトを更新し、マイクロタイムを追加して、SELECT, UPDATE and CHECK-(SELECT)
一緒にかかる時間を確認しました。
最初のメンバー (ID 20468 を持つ) が電話をかける: 2013-10-01 08:30:10
次の 2 つの ID の 2/2 行が正しく更新されました: 33412、33395
クエリにかかった合計 0.878005027771 秒
2 番目のメンバー (ID 10123 を持つ) が次の場所に電話をかけます: 2013-10-01 08:30:14
次の 22 の ID の 20/22 行が正しく更新されました: 33392、33412、33395、33396、41489、13011、12555、27971、22811 など、重要ではありません。
クエリにかかった時間 3.3440849781036 秒
ここで、33412と33395が SELECT によって再び返されることがわかります。
3 番目のメンバー (ID 20951 を持つ) が電話をかけます: 2013-10-01 08:30:16
次の 9 つの ID の 9/9 行が正しく更新されました: 33392,33412,33395,33396,41489,13011,12555,27971,22811
クエリをまとめました私も少し気になるものは返されませんでした
最後のクエリにかかった時間がわからないので、1 番目と 2 番目のクエリが問題なく正しく動作することだけがわかります。実行時間は 3.34 秒でした。それに加えて、最初の通話は2013-10-01 08:30:17に開始されました。これは、通話 (メール送信時) がスクリプトの最後に記録されているためです。クエリにかかった時間を確認するのは、最初のクエリの開始から最後のクエリの直後の停止までであり、これはメールを送信する前です (もちろん)。
my.cnf
mysqlがこれを奇妙なことをしているのは、ファイル内の何かでしょうか? それでも、id が最後の (3 番目の) 呼び出しの実行時間を返さなかった理由がわかりません。これに対する解決策は、最初にこれらのアクションをテーブルに保存し、cron ジョブによって一度に 1 つずつ実行することによって、これらのアクションをキューに入れることです。しかし、それは私が本当に望んでいるものではありません.メンバーが電話をかけたときにすぐにすべきです. これまでご協力いただきありがとうございました。
とにかく、my.cnf
誰かが私に提案がある場合に備えて、ここに私のものがあります(サーバーには16GBのRAMがインストールされています):
[client]
port = 3306
socket = /var/run/mysqld/mysqld.sock
[mysqld_safe]
socket = /var/run/mysqld/mysqld.sock
nice = 0
[mysqld]
user = mysql
pid-file = /var/run/mysqld/mysqld.pid
socket = /var/run/mysqld/mysqld.sock
port = 3306
basedir = /usr
datadir = /var/lib/mysql
tmpdir = /tmp
lc-messages-dir = /usr/share/mysql
skip-external-locking
key_buffer = 16M
max_allowed_packet = 16M
thread_stack = 192K
thread_cache_size = 8
myisam-recover = BACKUP
max_connections = 20
query_cache_type = 1
query_cache_limit = 1M
query_cache_size = 4M
log_error = /var/log/mysql/error.log
expire_logs_days = 10
max_binlog_size = 100M
innodb_buffer_pool_size = 333M
join_buffer_size = 128K
tmp_table_size = 16M
max_heap_table_size = 16M
table_cache = 200
[mysqldump]
quick
quote-names
max_allowed_packet = 16M
[mysql]
#no-auto-rehash # faster start of mysql but no tab completition
[isamchk]
key_buffer = 16M
!includedir /etc/mysql/conf.d/
編集2:
$recycle_available = $this->Account->Membership->query("
SELECT Account.id,
(SELECT COUNT(last_clicks.id) FROM last_clicks WHERE last_clicks.account = Account.id AND last_clicks.roulette = '0' AND last_clicks.date BETWEEN '".$seven_days_back."' AND '".$tomorrow."') AS total,
(SELECT COUNT(last_clicks.id)/7 FROM last_clicks WHERE last_clicks.account = Account.id AND last_clicks.roulette = '0' AND last_clicks.date BETWEEN '".$seven_days_back."' AND '".$tomorrow."') AS avg
FROM membership AS Membership
INNER JOIN account AS Account ON Account.id = Membership.account
WHERE Account.membership = '0' AND Account.referrer = '0' AND Membership.membership = '1'
HAVING avg > 0.9
ORDER BY total DESC");
foreach($referrals as $key => $value)
{
$this->Account->query("UPDATE account SET referrer='".$account_id."', since='".$since_date."', expires='".$value['Account']['expires']."', marker='0', kind='1', auction_amount='".$value['Account']['auction_amount']."' WHERE id='".$recycle_available[$key]['Account']['id']."'");
$new_referral_id[] = $recycle_available[$key]['Account']['id'];
$counter++;
}
$total_updated = $this->Account->find('count',array('conditions'=>array('Account.id'=>$new_referral_id, 'Account.referrer'=>$account_id, 'Account.kind'=>1)));