Вопрос

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

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

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

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

БЛОКИРОВКА ТАБЛИЦЫ звучит как перебор, особенно если таблица обновляется несколько раз в секунду.

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

Это было полезно?

Решение

вы хотите LOCK TABLES

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

  

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

Другие советы

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Вы очень просто предотвращаете дублирование строк, добавляя уникальные индексы в свои таблицы.Это не имеет никакого отношения к БЛОКИРОВКАМ или ТРАНЗАКЦИЯМ.

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

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

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

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

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

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

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

<Ол>
  • Пользователь A проверяет, зарезервирован ли билет, а не
  • Пользователь B проверяет, зарезервирован ли билет, а не
  • Пользователь B вставляет "зарезервированную" запись в таблицу для этого билета
  • Пользователь A вставляет «зарезервированную» запись в таблицу для этого билета
  • Пользователь B проверяет наличие дубликатов? Да, моя запись новее? Да, оставь это
  • Пользователь Проверка на наличие дубликатов? Да, моя запись новее? Нет, удалите его
  • Пользователь B зарезервировал билет, а пользователь A сообщает, что билет был получен кем-то другим.

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

    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top