MySQL и PHP: атомарность и повторяемость блока кода PHP, выполняющего два последующих запроса - насколько опасно? - PullRequest
2 голосов
/ 23 июня 2010

В MySQL я должен проверить, вернул ли запрос select какие-либо записи, если нет, я вставляю запись.Однако я боюсь, что вся операция if-else в сценариях PHP НЕ настолько атомарна, как хотелось бы, то есть будет прерываться в некоторых сценариях, например, если вызывается другой экземпляр сценария, где необходимо работать с той же записью:

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

Я не использовал транзакции здесь, и автокоммит включен.Я использую MySQL 5.1 с PHP 5.3.Таблица InnoDB.Я хотел бы знать, является ли приведенный выше код неоптимальным и действительно сломается.Я имею в виду, что один и тот же сценарий повторно вводится двумя экземплярами, и происходит следующая последовательность запросов:

  1. экземпляр 1 пытается выбрать запись, не находит ни одного, входит в блок для запроса вставки
  2. экземпляр 2 пытается выбрать запись, не находит ничего, входит в блок для запроса вставки
  3. экземпляр 1 пытается вставить запись, успешно
  4. экземпляр 2 пытается вставить запись, не удается, прерываетсяскрипт автоматически

Это означает, что экземпляр 2 будет прерван и вернет ошибку, пропуская что-либо после оператора запроса вставки.Я мог бы сделать ошибку не фатальной, но мне не нравится игнорировать ошибки, я бы скорее хотел знать, реальны ли мои страхи здесь.

Обновление: что я в итоге делал (это нормально дляТАК?)

Данная таблица помогает регулировать (разрешать / отклонять, действительно) количество сообщений, отправляемых приложением каждому получателю.Система не должна отправлять более X сообщений получателю Y в течение периода Z. Таблица [концептуально] выглядит следующим образом:

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 и не блокировать всю таблицу, что я не хочу делать из-за более частых обновлений / вставок по сравнению свыбирает. * 1 028 *

Ответы [ 2 ]

3 голосов
/ 23 июня 2010

Похоже, вы уже знаете ответ на вопрос, и как решить свою проблему. Это реальная проблема, и вы можете использовать одно из следующих для ее решения:

  • ВЫБРАТЬ ... ДЛЯ ОБНОВЛЕНИЯ
  • INSERT ... ON DUPLICATE KEY UPDATE
  • транзакций (не используйте MyIsam)
  • настольные замки
1 голос
/ 23 июня 2010

Это может и может произойти в зависимости от того, как часто выполняется эта страница.

Безопасной ставкой будет использование транзакций. То же самое, что вы написали, все равно будет происходить, за исключением того, что вы можете безопасно проверить на наличие ошибки внутри транзакции (в случае, если вставка включает в себя несколько запросов и только последние разрывы вставки), позволяя откатить тот, который стал недействительным.

Итак (псевдо):

#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 несуществующие строки, извините), но это также Блок читает из вашей таблицы, пока вы не закончите.

...