2008-11-05 3 views
10

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

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

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

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

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

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

ответ

9

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

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

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

+0

Идите с INSERT IGNORE. Триггеры вызывают слишком много накладных расходов, они не стоят этого, если бизнес-логика не является сложной. – captainspi 2013-06-08 21:30:03

+0

Что не так с помощью `INSERT`-ing с уникальным ограничением и ловушками ошибок, чтобы сказать« хорошо, запись уже существует ».? – KeatsKelleher 2014-05-22 14:03:11

4

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

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

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

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

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

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

+0

Ну да, конечно, у нас есть уникальные отличия и т. Д. ... на самом деле это и заставило нас осознать, что проблема существует, это ошибки, вызванные уникальным индексом. – tpk 2008-11-05 10:59:35

+2

Вот как это должно работать, вы готовитесь к транзакциям, чтобы потерпеть неудачу ... в этом случае это кажется очень простым: если ошибка ключа dup, проигнорируйте, потому что строка уже существует. Полная блокировка таблицы/строки может быть более высокой, чем просто игнорирование ошибок при их возникновении. Мера, хотя – 2008-11-05 11:14:48

2

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

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

0

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

Наконец я придумал решение, аналогичное способу creating filesystem objects in shared (temporary) directories to securely open temporary files:

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

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

3

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

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

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

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

0

Вы предотвратить повторяющиеся строки очень просто добавляя уникальные индексы на ваши таблицы. Это не имеет никакого отношения к LOCKS или ОПЕРАЦИЯМ.

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

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

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

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

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

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

0

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

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

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

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

Смежные вопросы