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

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

3.0 Senior🔥 101 комментариев
#Архитектура и паттерны#Базы данных и SQL

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

🐱
claude-haiku-4.5PrepBro AI30 мар. 2026 г.(ред.)

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

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

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

Оптимистичная блокировка это техника для управления конфликтами в многопользовательских системах без использования database locks. Предполагается что конфликты редки и проверяются только при сохранении.

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

1. Пользователь А читает данные (version = 1)
2. Пользователь Б читает те же данные (version = 1)
3. Пользователь А обновляет и сохраняет (version -> 2)
4. Пользователь Б пытается обновить
   - Проверка: текущая версия 2, а он хочет обновить с версии 1
   - КОНФЛИКТ! Не позволяем обновление
5. Пользователь Б должен перечитать и повторить

Пример с version column

-- Таблица users с version column
CREATE TABLE users (
  id INTEGER PRIMARY KEY,
  name VARCHAR(100),
  email VARCHAR(100),
  version INTEGER DEFAULT 1
);

-- Исходные данные
INSERT INTO users (id, name, email, version) 
VALUES (1, 'John', 'john@example.com', 1);

-- Пользователь А: читает
SELECT * FROM users WHERE id = 1;
-- Получает: id=1, name=John, email=john@example.com, version=1

-- Пользователь Б: читает то же
SELECT * FROM users WHERE id = 1;
-- Получает: id=1, name=John, email=john@example.com, version=1

-- Пользователь А: обновляет
UPDATE users 
SET name = 'John Updated', version = version + 1
WHERE id = 1 AND version = 1;
-- Успешно! version стал 2

-- Пользователь Б: пытается обновить с версией 1
UPDATE users 
SET email = 'newemail@example.com', version = version + 1
WHERE id = 1 AND version = 1;
-- ОШИБКА! Ноль строк обновлено (version = 2, а он хочет 1)

Реализация в Node.js

// Модель
class User {
  id: number;
  name: string;
  email: string;
  version: number;
}

// Сервис с оптимистичной блокировкой
class UserService {
  async updateUser(
    id: number,
    data: Partial<User>,
    expectedVersion: number
  ): Promise<User> {
    // UPDATE ... WHERE id = ? AND version = ?
    const result = await db.query(
      `UPDATE users 
       SET name = ?, email = ?, version = version + 1 
       WHERE id = ? AND version = ?`,
      [data.name, data.email, id, expectedVersion]
    );
    
    // Если не обновлено ни одной строки - конфликт версии
    if (result.affectedRows === 0) {
      throw new OptimisticLockException(
        `Version mismatch for user ${id}`
      );
    }
    
    return this.getUser(id);
  }
  
  async getUser(id: number): Promise<User> {
    const [user] = await db.query(
      'SELECT * FROM users WHERE id = ?',
      [id]
    );
    return user;
  }
}

// Использование
const service = new UserService();

try {
  // 1. Читаем текущие данные
  const user = await service.getUser(1);
  // { id: 1, name: 'John', email: 'john@example.com', version: 1 }
  
  // 2. Обновляем (передаём ожидаемую версию)
  const updated = await service.updateUser(
    user.id,
    { email: 'newemail@example.com' },
    user.version // Ожидаем версию 1
  );
  
  console.log('Успешно обновлено');
  console.log(updated.version); // 2
} catch (error) {
  if (error instanceof OptimisticLockException) {
    console.log('Конфликт версии! Перечитайте данные и попробуйте снова');
  }
}

Оптимистичная блокировка в ORM (Sequelize/TypeORM)

// TypeORM с версионированием
import { Entity, Column, Version } from 'typeorm';

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @Column()
  email: string;

  @Version()
  version: number; // Автоматически управляется
}

// Использование
const user = await userRepository.findOne(1);
user.email = 'newemail@example.com';

try {
  await userRepository.save(user);
  console.log('Успешно');
} catch (error) {
  if (error.code === 'OPTIMISTIC_LOCK_FAILED') {
    console.log('Конфликт версии!');
  }
}

Оптимистичная vs Пессимистичная блокировка

ОПТИМИСТИЧНА (Optimistic Locking):
✅ Нет блокировок в БД
✅ Высокая производительность
✅ Хорошо для редких конфликтов
❌ Нужно обрабатывать конфликты
❌ Нужен столбец версии

ПЕССИМИСТИЧНА (Pessimistic Locking):
✅ Гарантирует отсутствие конфликтов
✅ Простая в использовании
❌ Блокирует строку (другие ждут)
❌ Медленнее при высокой конкуренции
❌ Риск deadlock'ов

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

-- SELECT FOR UPDATE - блокирует строку
SELECT * FROM users WHERE id = 1 FOR UPDATE;

-- Другой пользователь ЖДЁТ пока первый не закончит транзакцию
BEGIN;
SELECT * FROM users WHERE id = 1 FOR UPDATE;
-- ... он это прочитает
UPDATE users SET name = 'New' WHERE id = 1;
COMMIT; -- Теперь второй пользователь может продолжить

Когда использовать

✅ ОПТИМИСТИЧНУЮ блокировку когда:
- Конфликты редки
- Много читателей, мало писателей
- Нужна высокая производительность
- Пример: обновление профиля пользователя

✅ ПЕССИМИСТИЧНУЮ блокировку когда:
- Конфликты частые
- Много одновременных обновлений
- Нельзя повторять операции
- Пример: бронирование билетов, перевод денег

Типы версионирования

// 1. Простой счётчик версии (самый частый)
version: 1 -> 2 -> 3 -> 4 ...

// 2. Временная метка
last_updated: '2024-01-01T10:00:00Z'

// 3. Хеш содержимого
data_hash: 'abc123def456'

// 4. ETag (в HTTP)
ETag: 'W/"123abc"'

Обработка конфликтов

// Автоматический retry
async function updateWithRetry(
  id: number,
  updates: Partial<User>,
  maxRetries: number = 3
): Promise<User> {
  for (let i = 0; i < maxRetries; i++) {
    try {
      const user = await getUser(id);
      return await updateUser(id, updates, user.version);
    } catch (error) {
      if (error instanceof OptimisticLockException && i < maxRetries - 1) {
        // Retry с задержкой
        await delay(100 * (i + 1));
        continue;
      }
      throw error;
    }
  }
}

// Merge конфликтных изменений
async function mergeUpdates(
  id: number,
  updates: Partial<User>
): Promise<User> {
  const current = await getUser(id);
  const merged = { ...current, ...updates };
  return updateUser(id, merged, current.version);
}

Выводы

Оптимистичная блокировка:

  • Предполагает что конфликты редки
  • Использует столбец версии (или временную метку)
  • Проверяет версию при обновлении
  • Если версия не совпадает выбрасывает ошибку
  • Отличная производительность без БД блокировок
  • Нужна правильная обработка конфликтов
Что такое оптимистичная блокировка? | PrepBro