условие гонки вставки mysql

Как остановить условия гонки в MySQL? проблема под рукой вызвана простым алгоритмом:

  1. выбрать строку из таблицы
  2. если он не существует, вставить

и затем либо вы получаете дублирующую строку, либо, если вы предотвращаете ее с помощью уникальных/первичных ключей, ошибка.

теперь обычно я думаю, что транзакции помогают здесь, но поскольку строка не существует, транзакция на самом деле не помогает (или мне не хватает что-то?).

блокировка таблицы звучит как перебор, особенно если таблица обновляется несколько раз в секунду.

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

7 ответов


Что вы хотите ЗАБЛОКИРОВАТЬ ТАБЛИЦЫ

или если это кажется чрезмерным, как о ВСТАВИТЬ ИГНОРИРОВАТЬ с проверкой того, что строка была фактически вставлена.

Если вы используете ключевое слово IGNORE, ошибки которые возникают при выполнении INSERT инструкции рассматриваются как предупреждения вместо.


Мне кажется, что у вас должен быть уникальный индекс в столбце id, поэтому повторная вставка вызовет ошибку, а не будет слепо принята снова.

Это можно сделать, определив id как первичный ключ или используя уникальный индекс сам по себе.

Я думаю, что первый вопрос, который вам нужно задать, - почему у вас много потоков, выполняющих одну и ту же работу? Зачем им вставлять одну и ту же строку?

после этого ответа, я думаю, что просто игнорирование ошибок будет наиболее эффективным решением, но измерьте оба подхода (GET_LOCK V/S игнорирует ошибки) и убедитесь сами.

другого способа я не знаю. Почему вы хотите избежать ошибок? Вам все равно придется кодировать случай, когда возникает другой тип ошибки.

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


блокировка всей таблицы-это действительно перебор. Чтобы получить эффект, который вы хотите, вам нужно что-то, что litterature называет "предикатными блокировками". Никто никогда не видел их, кроме как напечатанных на бумаге, на которой публикуются академические исследования. Следующая лучшая вещь-это блокировки " путей доступа "к данным (в некоторых СУБД:"блокировки страниц").

некоторые системы, отличные от SQL, позволяют выполнять оба (1) и (2) в одном операторе, что более или менее означает потенциальные условия гонки возникающие из вашей ОС, приостанавливающей поток выполнения прямо между (1) и (2), полностью устраняются.

тем не менее, при отсутствии предикатных блокировок таким системам все равно придется прибегать к какой-то схеме блокировки, и чем тоньше "гранулярность" (/"область") блокировок, тем лучше для параллелизма.

(и в заключение: некоторые СУБД - особенно те, за которые вам не нужно платить - действительно не предлагают более тонкой детализации блокировки, чем " весь таблица.")


на техническом уровне транзакция поможет здесь, потому что другие потоки не увидят новую строку, пока вы не зафиксируете транзакцию.

но на практике это не решить проблема - это только движется его. Теперь ваше приложение должно проверить, не завершилась ли фиксация, и решить, что делать. Обычно я бы откатил то, что вы сделали, и перезапустил транзакцию, потому что теперь строка будет видна. Вот как работает программист на основе транзакций должно сработать.


я столкнулся с той же проблемой и некоторое время искал в Сети :)

наконец, я придумал решение, подобное методу создание объектов файловой системы в общих (временных) каталогах для безопасного открытия временных файлов:

$exists = $success = false;
do{
 $exists = check();// select a row in the table 
 if (!$exists)
  $success = create_record();
  if ($success){
   $exists = true;
  }else if ($success != ERROR_DUP_ROW){
    log_error("failed to create row not 'coz DUP_ROW!");
    break;
  }else{
    //probably other process has already created the record,
    //so try check again if exists
  }
}while(!$exists)

Не бойся занят-loop - обычно он будет выполняться один или два раза.


вы предотвращаете дублирование строк очень просто, помещая уникальные индексы в свои таблицы. Это не имеет ничего общего с блокировками или транзакциями.

вас волнует, если вставка терпит неудачу, потому что это дубликат? Вы должны быть уведомлены, если это не удастся? Или все, что имеет значение, что строка была вставлена,и не имеет значения, кем или сколько дубликатов вставок не удалось?

Если вам все равно, то все, что вам нужно, это INSERT IGNORE. Нет необходимости думать о транзакциях или таблице замки вообще.

InnoDB имеет автоматическую блокировку уровня строки, но это относится только к обновлениям и удалениям. Вы правы, что это не относится к вставкам. Нельзя запереть то, чего еще нет!

вы можете явно LOCK всю таблицу. Но если ваша цель-предотвратить дубликаты, то вы делаете это неправильно. Опять же, используйте уникальный индекс.

если есть набор изменений, которые необходимо сделать, и вы хотите результат "все или ничего" (или даже набор all-or-nothing приводит к большему результату all-or-nothing), затем используйте транзакции и точки сохранения. Тогда используйте ROLLBACK или ROLLBACK TO SAVEPOINT *savepoint_name* отменить изменения, включая удаления, обновления и вставками.

LOCK таблицы не заменяют транзакции, но это ваш единственный вариант с таблицами MyISAM, которые не поддерживают транзакции. Вы также можете использовать его с таблицами InnoDB, если блокировки уровня строки недостаточно. См.на этой странице для получения дополнительной информации о использование транзакций с операторами lock table.


У меня аналогичная проблема. У меня есть таблица, которая в большинстве случаев должна иметь уникальное значение ticket_id, но есть случаи, когда у меня будут дубликаты; не лучший дизайн, но это то, что есть.

  1. пользователь A проверяет, зарезервирован ли билет, это не
  2. пользователь B проверяет, зарезервирован ли билет, это не
  3. пользователь B вставляет "зарезервированную" запись в таблицу для этого билета
  4. пользователь A вставляет "зарезервировано" запишите в таблицу для этого билета
  5. пользователь B проверить дубликат? Да, моя пластинка новее? Да, оставь это
  6. пользователь проверяет дубликат? Да, моя пластинка новее? Нет, удалите его

пользователь B зарезервировал билет, пользователь A сообщает, что билет был взят кем-то другим.

ключ в моем случае заключается в том, что вам нужен тай-брейкер, в моем случае это идентификатор автоматического приращения в строке.