Mysql insert race состояние

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

  • выберите строку из таблицы
  • если он не существует, вставьте его

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

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

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

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

Ответ 1

то, что вы хотите, LOCK TABLES

или если это кажется чрезмерным, как о INSERT IGNORE с проверкой того, что строка была фактически вставлена.

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

Ответ 2

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

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

Я думаю, что первый вопрос, который вам нужно задать, - это то, почему у вас много потоков, выполняющих ТОЧНУЮ РАБОТУ? Зачем им нужно вставлять одну и ту же строку?

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

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

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

Ответ 3

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

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

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

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

Ответ 4

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

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

Ответ 5

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

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

$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)

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

Ответ 6

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

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

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

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

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

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

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

Ответ 7

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

  • Пользователь A проверяет, зарезервирован ли билет, это не
  • Пользователь B проверяет, зарезервирован ли билет, это не
  • Пользователь B вставляет зарезервированную запись в таблицу для этого билета
  • Пользователь A вставляет зарезервированную запись в таблицу для этого билета
  • Пользователь B проверяет наличие дубликатов? Да, мой рекорд? Да, оставьте его
  • Пользователь Проверка на дублирование? Да, мой рекорд? Нет, удалите его.

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

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