Я пытаюсь реализовать простую блокировку мьютекса в SQL. Мое предварительное решение следующее:
query = 'UPDATE my_table SET lock_field = 1 WHERE lock_field = 0 AND mutex_id = 17'
if (query.execute() == 1){
// We own the lock, continue processing
// Finally: 'UPDATE my_table SET lock_field = 0 WHERE mutex_id = 17'
} else {
// Somebody else owns the lock, backoff and wait or throw exception etc
}
Мой вопрос: безопасен ли этот код? Гарантирует ли это, что только один «клиент» может владеть замком?
Отредактировано в ответ на комментарии, чтобы уточнить использование в качестве мьютекса, а не обычной обработки БД.
Это только доставит вам массу хлопот.
«кажется намного проще, чем транзакции», как это проще? Очистку невозможно гарантировать, и она гораздо менее эффективна, поскольку требует обязательной записи на диск, а не блокировки в памяти.
Все справедливо, если немного кратко. А если серьезно, на данный момент -2 отрицательных голоса за вопрос, который многие другие люди, вероятно, зададут/обнаружат отдельно? Как другие узнают, что это опасно, если вопрос скрыт?
Такой подход безопасен, поскольку гарантирует, что никакие два клиента не смогут получить «блокировку» одновременно. Проблема в том, что на практике этого недостаточно, поскольку это не может гарантировать, что хотя бы один клиент когда-либо сможет получить блокировку; в какой-то момент база данных перестанет выполнять полезную работу. Обратите внимание, что база данных, не выполняющая никакой работы, поддерживает упомянутую выше гарантию.
Причина в том, что невозможно гарантировать, что замок когда-либо будет разблокирован. Рассмотрим последовательность событий
1. `SET lock_field = 1`
2. `query.execute() == 1` is true
3. OutOfMemoryException
4. Thread raised exception within exception, process aborted, destructor did not run
5. The row is now locked forever
Невозможно определить, исчез ли клиент, заблокировавший строку, прервался, сбросил ли сетевое соединение, попал в бесконечный цикл или просто не разблокировался из-за ошибки. Строго говоря, упомянутая вами гарантия сохраняется, но никакой полезной работы произвести невозможно.
Единственный выход из этого — догадаться, что клиент пропал. Надеюсь, клиент действительно ушел. Но тогда мы не можем быть уверены, что данные, защищенные блокировкой, находятся в согласованном состоянии.
Короче говоря: нет, такие схемы на практике не работают. Не пытайтесь заставить их работать; они этого не делают. Сама база данных полностью оборудована для того, чтобы гарантировать изоляцию транзакций и одновременно гарантировать дальнейшее продвижение.
Вы правы в том, что мертвый клиент навсегда заблокирует эту запись. У нас есть полуручные процессы для обработки таких случаев. Наша главная проблема заключается в том, чтобы (максимум) только один клиент мог получить доступ к записи одновременно. Кроме того, нам нужно будет выполнить очистку в другом месте, если клиент выйдет из строя, поэтому постоянная блокировка записи в случае сбоя может рассматриваться как функция безопасности.
Если ваша база данных может гарантировать, что SET lock_field = 1 WHERE lock_field = 0
выполняется атомарно. Обратите внимание, что детали запроса имеют значение.
Да! В этом и суть моего вопроса (атомарность запроса на обновление). Я не могу найти нигде никаких утверждений о том, что это верно для очень простого запроса, приведенного выше. Первая строка вашего ответа на самом деле адекватно отвечает на вопрос, и я принимаю ваши предупреждения о тупиковой ситуации.
Обратите внимание, что это верно только в том случае, если вызов .execute()
фиксирует UPDATE
. См. здесь о том, как и почему два клиента могли видеть lock_field = 0
и оба успешно UPDATE
одну и ту же строку. В зависимости от драйвера вашей базы данных вызов execute()
может автоматически зафиксироваться, но вам следует это проверить.
Это ужасная идея! Используйте транзакции базы данных по назначению.