Что такое оптимистическая и пессимистическая блокировки?

Ответ

Оптимистическая и пессимистическая блокировки — это две стратегии управления одновременным доступом к данным (concurrency control) для предотвращения конфликтов.

Оптимистическая блокировка (Optimistic Locking)

Идея: Конфликты происходят редко. Система не блокирует данные при чтении, а «надеется» на лучшее. Блокировка происходит только в момент записи, причем система проверяет, не изменились ли данные с момента их чтения.

Как работает:

  1. Клиент читает данные вместе с их версией (числом или timestamp).
  2. Клиент выполняет необходимые изменения в приложении.
  3. При попытке записать изменения клиент отправляет новые данные и версию, которую он прочитал.
  4. Система обновляет запись только если текущая версия в базе данных совпадает с той, что прислал клиент. После успешного обновления версия увеличивается.
  5. Если версии не совпадают, значит, другой клиент уже успел изменить данные. Транзакция откатывается, и клиенту возвращается ошибка.

Пример в Go (псевдокод):

// 1. Читаем запись и ее версию
var version int
err := db.QueryRow("SELECT name, version FROM products WHERE id = 1").Scan(&name, &version)

// 2. Пытаемся обновить, проверяя версию в WHERE
result, err := db.Exec(
    "UPDATE products SET name = $1, version = version + 1 WHERE id = $2 AND version = $3", 
    "New Name", 1, version,
)

// 3. Проверяем, была ли строка обновлена
rowsAffected, _ := result.RowsAffected()
if rowsAffected == 0 {
    return errors.New("конфликт обновления: данные были изменены другим пользователем")
}
  • Плюсы: Высокая производительность при низкой интенсивности конфликтов, так как нет долгих блокировок.
  • Минусы: При высокой интенсивности конфликтов много транзакций будут откатываться, что снижает эффективность.

Пессимистическая блокировка (Pessimistic Locking)

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

Как работает:

  1. Клиент начинает транзакцию и запрашивает данные с явной блокировкой (например, SELECT ... FOR UPDATE).
  2. База данных блокирует выбранные строки. Другие транзакции, пытающиеся получить доступ к этим строкам, будут ждать снятия блокировки.
  3. Клиент выполняет изменения и завершает транзакцию (commit или rollback).
  4. Блокировка снимается.
  • Плюсы: Гарантирует, что транзакция завершится успешно, если она получила блокировку. Идеально для сред с высокой вероятностью конфликтов.
  • Минусы: Снижает параллельность работы системы. Может приводить к deadlock-ам (взаимным блокировкам).

Ответ 18+ 🔞

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

Оптимистическая блокировка (Optimistic Locking)

Вот представь, идешь ты в магазин за последней бутылкой водки. Оптимист такой думает: «Да кому она нахуй нужна, кроме меня? Конфликтов же не будет!» Берёшь её с полки, смотришь на ценник — это типа версия товара, блядь. Идёшь к кассе, а там уже очередь. Пока стоишь, представляешь, как её будешь распивать. Подходишь, а кассирша такая: «Ой, а эту только что последний мужик просканировал, извините». И всё, пиздец твоей мечте. Транзакция откатилась, иди нахуй.

Как оно на самом деле работает:

  1. Считал ты данные и их версию (как тот ценник).
  2. Поменял что-то у себя в голове (или в приложении).
  3. Прибежал записывать, сука, а там говорят: «А пока ты тут думал, версию уже сменили!».
  4. Обновят твои данные только если версия совпала. Не совпала — получай ошибку и иди перечитывай всё заново, лох.

Вот, смотри, как это в коде выглядит, ёпта:

// 1. Читаем запись и ее версию
var version int
err := db.QueryRow("SELECT name, version FROM products WHERE id = 1").Scan(&name, &version)

// 2. Пытаемся обновить, проверяя версию в WHERE
result, err := db.Exec(
    "UPDATE products SET name = $1, version = version + 1 WHERE id = $2 AND version = $3", 
    "New Name", 1, version,
)

// 3. Проверяем, была ли строка обновлена
rowsAffected, _ := result.RowsAffected()
if rowsAffected == 0 {
    return errors.New("конфликт обновления: данные были изменены другим пользователем")
}
  • Плюсы: Быстро, если все друг другу не мешают. Как на широкой дороге — все едут и не дерутся.
  • Минусы: А если на той же бутылке водки очередь? Все будут её хватать, а потом откатываться с ошибкой. Сплошные конфликты, эффективность — ноль ебать.

Пессимистическая блокировка (Pessimistic Locking)

А это уже подход моего деда, который в советское время за дефицитом гонялся. Подошёл к прилавку, схватил банку тушёнки и держит, сука, как маньяк. «Моё! Никому не отдам!». И стоит так, пока не расплатится. Все остальные вокруг ходят, злые, но нихуя не могут сделать — он же первый схватил и не отпускает.

Как это в системе:

  1. Начинаешь транзакцию и сразу говоришь: «Дай мне эти данные И ЗАБЛОКИРУЙ ИХ, БЛЯДЬ, НИКОМУ БОЛЬШЕ!» (SELECT ... FOR UPDATE).
  2. База данных берёт и навешивает замок на эти строки. Все, кто после пришли, — ждут, как лохи.
  3. Ты неспеша делаешь свои дела, меняешь данные.
  4. Закончил — закрыл транзакцию, отпустил блокировку. Только тогда следующий в очереди может попробовать схватить.
  • Плюсы: Гарантия, что если уж ты вцепился — то доведёшь дело до конца. Для ситуаций, где все друг другу готовы морду набить за ресурс.
  • Минусы: Параллельность работы летит в пизду. Все стоят и ждут. А если два таких параноика схватятся друг за друга (один заблокировал A и хочет B, а второй заблокировал B и хочет A) — вот вам и deadlock, ёперный театр! Сидят и смотрят друг на друга, как дураки.

Итог, блядь:
Если у тебя народ культурный и редко друг другу мешает — бери оптимистическую, будет быстро.
А если у тебя там, блядь, дикий запад, все лезут одновременно в одну запись — ставь пессимистическую, и пусть ждут своей очереди, пидарасы.
Вот и вся философия, в рот меня чих-пых.