Возможно ли/безопасно в SQL реализовать оптимистическую блокировку, используя логическое значение вместо приращения версии?

Я пытаюсь реализовать простую блокировку мьютекса в 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
}

Мой вопрос: безопасен ли этот код? Гарантирует ли это, что только один «клиент» может владеть замком?

Отредактировано в ответ на комментарии, чтобы уточнить использование в качестве мьютекса, а не обычной обработки БД.

Это ужасная идея! Используйте транзакции базы данных по назначению.

Mitch Wheat 13.07.2024 15:24

Это только доставит вам массу хлопот.

jarlh 13.07.2024 20:08

«кажется намного проще, чем транзакции», как это проще? Очистку невозможно гарантировать, и она гораздо менее эффективна, поскольку требует обязательной записи на диск, а не блокировки в памяти.

Charlieface 14.07.2024 02:22

Все справедливо, если немного кратко. А если серьезно, на данный момент -2 отрицательных голоса за вопрос, который многие другие люди, вероятно, зададут/обнаружат отдельно? Как другие узнают, что это опасно, если вопрос скрыт?

BlueBiscuit 15.07.2024 10:40
ReactJs | Supabase | Добавление данных в базу данных
ReactJs | Supabase | Добавление данных в базу данных
Это и есть ваш редактор таблиц в supabase.👇
Понимание Python и переход к SQL
Понимание Python и переход к SQL
Перед нами лабораторная работа по BloodOath:
2
4
55
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Такой подход безопасен, поскольку гарантирует, что никакие два клиента не смогут получить «блокировку» одновременно. Проблема в том, что на практике этого недостаточно, поскольку это не может гарантировать, что хотя бы один клиент когда-либо сможет получить блокировку; в какой-то момент база данных перестанет выполнять полезную работу. Обратите внимание, что база данных, не выполняющая никакой работы, поддерживает упомянутую выше гарантию.

Причина в том, что невозможно гарантировать, что замок когда-либо будет разблокирован. Рассмотрим последовательность событий

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

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

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

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

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

BlueBiscuit 13.07.2024 15:00

Если ваша база данных может гарантировать, что SET lock_field = 1 WHERE lock_field = 0 выполняется атомарно. Обратите внимание, что детали запроса имеют значение.

user2722968 13.07.2024 15:13

Да! В этом и суть моего вопроса (атомарность запроса на обновление). Я не могу найти нигде никаких утверждений о том, что это верно для очень простого запроса, приведенного выше. Первая строка вашего ответа на самом деле адекватно отвечает на вопрос, и я принимаю ваши предупреждения о тупиковой ситуации.

BlueBiscuit 13.07.2024 15:39

Обратите внимание, что это верно только в том случае, если вызов .execute() фиксирует UPDATE. См. здесь о том, как и почему два клиента могли видеть lock_field = 0 и оба успешно UPDATE одну и ту же строку. В зависимости от драйвера вашей базы данных вызов execute() может автоматически зафиксироваться, но вам следует это проверить.

user2722968 13.07.2024 16:31

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