← Назад к вопросам

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

2.2 Middle🔥 142 комментариев
#Базы данных

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

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

Оптимистичная блокировка — это стратегия управления параллельным доступом к данным в распределённых системах и базах данных, которая предполагает, что конфликты при одновременном изменении одних и тех же данных возникают редко. В отличие от пессимистичной блокировки, которая заранее блокирует ресурс на время всей транзакции, оптимистичный подход не блокирует данные на этапе чтения. Вместо этого он проверяет, не изменились ли данные с момента их чтения, в момент фиксации изменений (commit). Если изменения были — транзакция отклоняется, и клиент должен повторить операцию.

Принцип работы

Основной механизм строится на трёх этапах:

  1. Чтение данных и версии: Клиент считывает необходимые данные из хранилища (например, строку таблицы) вместе со специальным полем-версией (обычно числовым счетчиком, timestamp или хэшем).
  2. Внесение изменений локально: Клиент обрабатывает данные и готовится записать изменения.
  3. Попытка записи (проверка и коммит): При попытке сохранения отправляется запрос на обновление, который в WHERE-условии проверяет, что версия данных осталась той же, что и при чтении. Одновременно версия инкрементируется.
-- Пример SQL-запроса для оптимистичного обновления
UPDATE products
SET
    name = 'Новое название',
    price = 1999,
    version = version + 1 -- Увеличиваем версию
WHERE
    id = 123
    AND version = 5; -- Проверяем, что версия не изменилась

Ключевой момент: Если строка с id=123 и version=5 больше не существует (потому что другой процесс уже обновил запись и увеличил версию до 6), то количество затронутых строк (rows affected) будет равно 0. Это сигнализирует клиентскому приложению о конфликте параллельного доступа.

Реализация в Go (GORM)

В Go, при использовании популярного ORM GORM, механизм оптимистичной блокировки встроен и использует поле с тегом gorm:"version".

package main

import (
    "fmt"
    "gorm.io/driver/sqlite"
    "gorm.io/gorm"
    "gorm.io/gorm/clause"
)

type Product struct {
    ID      uint   `gorm:"primarykey"`
    Name    string
    Price   int
    Version int    `gorm:"version"` // Включение управления версиями
}

func main() {
    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    if err != nil {
        panic(err)
    }
    db.AutoMigrate(&Product{})

    // Начинаем "транзакцию" в бизнес-логике (без БД-транзакции)
    var product Product
    db.First(&product, 1) // Чтение: product.Version = N

    // Симуляция задержки, во время которой другой процесс обновит запись
    // time.Sleep(2 * time.Second)

    product.Price = 2000

    // Попытка обновления с проверкой версии
    result := db.Model(&Product{}).
        Where("id = ? AND version = ?", product.ID, product.Version).
        Updates(map[string]interface{}{
            "price":   product.Price,
            "version": gorm.Expr("version + 1"),
        })

    if result.Error != nil {
        panic(result.Error)
    }
    if result.RowsAffected == 0 {
        // КОНФЛИКТ: Версия изменилась, данные устарели
        fmt.Println("Обновление не удалось: обнаружен конфликт версий. Необходимо повторить операцию.")
        // Здесь должна быть логика повтора: повторное чтение, применение изменений и новая попытка.
    } else {
        fmt.Println("Обновление успешно.")
    }
}

Преимущества и недостатки

Преимущества:

  • Высокая производительность при низкой конкуренции: Отсутствие блокировок на чтение позволяет большому числу клиентов одновременно читать данные.
  • Меньше взаимоблокировок (deadlocks): Так как исключительные блокировки не удерживаются долго.
  • Лучшая масштабируемость: Подходит для сценариев с высокой частотой чтения.

Недостатки:

  • Накладные расходы при конфликтах: Если конфликты часты, постоянные откаты и повторные попытки могут снизить производительность.
  • Усложнение клиентской логики: Приложение должно корректно обрабатывать ошибки конфликта (например, с помощью повторных попыток — retry-механизмов).
  • Не гарантирует успех операции: Пользователь может получить ошибку после выполнения работы, что требует продуманного UX.

Сравнение с пессимистичной блокировкой

КритерийОптимистичная блокировкаПессимистичная блокировка
ФилософияПроверяй при записиЗаблокируй при чтении
СценарийКонфликты редки, много чтенияКонфликты часты, критические изменения
ПроизводительностьВыше (нет блокировок на чтение)Ниже (ресурсы блокируются)
Риск deadlockНизкийВысокий
Пример SQLUPDATE ... WHERE version = NSELECT ... FOR UPDATE

Заключение

Оптимистичная блокировка — это мощный паттерн для обеспечения целостности данных в условиях высокого параллелизма без серьёзных потерь в производительности. В экосистеме Go она легко реализуется как на уровне SQL-запросов, так и с помощью ORM-библиотек. Её выбор оправдан в веб-приложениях, микросервисных архитектурах и системах, где операции чтения значительно превосходят операции записи, а вероятность коллизий относительно невелика. Ключ к успешному использованию — наличие в приложении надежного механизма повторных попыток и информирования пользователя о возможных конфликтах.

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