MySQL вставлять условия гонки - PullRequest
11 голосов
/ 05 ноября 2008

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

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

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

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

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

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

Ответы [ 7 ]

9 голосов
/ 05 ноября 2008

то, что вы хотите, это ЗАМКИ

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

Если вы используете ключевое слово IGNORE, ошибки которые происходят во время выполнения ВСТАВКИ Заявление рассматривается как предупреждение вместо этого.

4 голосов
/ 05 ноября 2008

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

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

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

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

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

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

3 голосов
/ 16 ноября 2009

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

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

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

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

2 голосов
/ 05 ноября 2008

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

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

0 голосов
/ 17 августа 2013

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

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

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

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

0 голосов
/ 14 января 2013

Вы очень просто предотвращаете дублирование строк, помещая уникальные индексы в свои таблицы. Это не имеет никакого отношения к ЗАМКАМ ​​или СДЕЛКАМ.

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

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

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

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

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

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

0 голосов
/ 21 августа 2009

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

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

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

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

...