2

MySQL では、select クエリがレコードを返したかどうかを確認する必要があります。そうでない場合は、レコードを挿入します。残念ながら、PHP スクリプトの if-else 操作全体は、私が望むほどアトミックではありません。たとえば、同じレコードを操作する必要がある場所でスクリプトの別のインスタンスが呼び出された場合など、いくつかのシナリオで壊れます。

if(select returns at least one record)
{
    update record;
}
else
{
    insert record;
}

ここではトランザクションを使用せず、自動コミットをオンにしています。PHP 5.3 で MySQL 5.1 を使用しています。テーブルは InnoDB です。上記のコードが最適ではなく、実際に壊れるかどうかを知りたいです。つまり、同じスクリプトが 2 つのインスタンスによって再入力され、次のクエリ シーケンスが発生します。

  1. インスタンス1はレコードを選択しようとしますが、何も見つかりません。挿入クエリのブロックに入ります
  2. インスタンス 2 はレコードを選択しようとしますが、何も見つかりません。挿入クエリのブロックに入ります。
  3. インスタンス 1 はレコードの挿入を試み、成功します
  4. インスタンス 2 がレコードを挿入しようとして失敗し、スクリプトを自動的に中止します

つまり、インスタンス 2 は中止され、エラーが返され、insert クエリ ステートメントに続くものはすべてスキップされます。エラーを致命的ではないようにすることもできますが、エラーを無視するのは好きではありません。

更新:私がやったこと(これはSOで大丈夫ですか?)

問題のテーブルは、アプリケーションが各受信者に送信するメッセージの量を調整 (実際には許可/拒否) するのに役立ちます。システムは、期間 Z 内に X 個を超えるメッセージを受信者 Y に送信してはなりません。表は [概念的に] 次のとおりです。

create table throttle
(
    recipient_id integer unsigned unique not null,
    send_count integer unsigned not null default 1,
    period_ts timestamp default current_timestamp,
    primary key (recipient_id)
) engine=InnoDB;

そして、テーブル内の適切なデータを維持し、スロットル状態に応じてメッセージの送信を許可/拒否するアトミック トランザクションを実行することになっている [やや簡略化/概念的な] PHP コードのブロック:

function send_message_throttled($recipient_id) /// The 'Y' variable
{
    query('begin');

    query("select send_count, unix_timestamp(period_ts) from throttle where recipient_id = $recipient_id for update");

    $r = query_result_row();

    if($r)
    {
        if(time() >= $r[1] + 60 * 60 * 24) /// The numeric offset is the length of the period, the 'Z' variable
        {/// new period
            query("update throttle set send_count = 1, period_ts = current_timestamp where recipient_id = $recipient_id");
        }
        else
        {
            if($r[0] < 5) /// Amount of messages allowed per period, the 'X' variable
            {
                query("update throttle set send_count = send_count + 1 where recipient_id = $recipient_id");
            }
            else
            {
                trigger_error('Will not send message, throttled down.', E_USER_WARNING);
                query('rollback');
                return 1;
            }
        }
    }
    else
    {
        query("insert into throttle(recipient_id) values($recipient_id)");
    }

    if(failed(send_message($recipient_id)))
    {
        query('rollback');
        return 2;
    }

    query('commit');
}

さて、InnoDB のデッドロックが発生するという事実を無視して、これはかなり良いことではないでしょうか? 私は胸をドキドキしているわけではありませんが、これは、MyISAM を使用してテーブル全体をロックすることを除いて、私ができるパフォーマンス/安定性の最良の組み合わせです。選択します。

4

2 に答える 2

3

質問に対する答えと、問題を解決する方法を既に知っているようです。これは実際の問題であり、次のいずれかを使用して解決できます。

  • SELECT ... FOR UPDATE
  • INSERT ... ON DUPLICATE KEY UPDATE
  • トランザクション (MyIsam を使用しないでください)
  • テーブルロック
于 2010-06-23T18:24:15.613 に答える
1

これ、このページが実行される頻度に応じて発生する可能性があります。

安全な方法は、トランザクションを使用することです。トランザクション内のエラーを安全にチェックできることを除いて(挿入に複数のクエリが含まれ、最後の挿入のみが中断する場合)、無効になったものをロールバックできます。

だから(疑似):

#start transaction
if (select returns at least one record)
{
    update record;
}
else
{
    insert record;
}

if (no constraint errors)
{
    commit; //ends transaction
}
else
{
    rollback; //ends transaction
}

テーブルをロックすることもできますが、行っている作業によっては、テーブル全体で排他ロックを取得する必要があります (SELECT ... FOR UPDATE存在しない行はできません、申し訳ありません)。あなたは終わった。

于 2010-06-23T18:23:22.177 に答える