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

Что блокируется при пессимистической блокировке

2.0 Middle🔥 131 комментариев
#ORM и Hibernate#Базы данных и SQL

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

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

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

Что блокируется при пессимистической блокировке

Пессимистическая блокировка (pessimistic locking) — это механизм для предотвращения одновременного доступа к данным. Давайте разберёмся, что именно блокируется и как это работает.

Основная идея

Пессимистическая блокировка исходит из предположения: конфликты вероятны, поэтому заблокируем данные сразу.

Transakcija 1:          Transakcija 2:
 SELECT ... FOR UPDATE  (ждёт)
 (читает и блокирует)   
 UPDATE                 
 COMMIT                 (теперь может читать)
                        SELECT ... FOR UPDATE

Что блокируется в БД

При выполнении SELECT ... FOR UPDATE блокируются строки в таблице:

-- Блокирует конкретные строки (rows)
SELECT * FROM users WHERE id = 1 FOR UPDATE;

Визуально в таблице:

users таблица:
+----+-------+-------+
| id | name  | age   |
+----+-------+-------+
| 1  | Alice | 30    |  ← ЭТА СТРОКА ЗАБЛОКИРОВАНА
| 2  | Bob   | 25    |
| 3  | Carol | 28    |
+----+-------+-------+

Виды блокировок в SQL

1. Shared Lock (S-lock) — Читающая блокировка

-- PostgreSQL
SELECT * FROM users WHERE id = 1 FOR SHARE;
Транзакция 1: SELECT FOR SHARE  → читает (может быть несколько)
Транзакция 2: SELECT FOR SHARE  → читает (может быть несколько)
Транзакция 3: UPDATE            → ждёт (конфликт)

Что блокируется: другие транзакции не могут ИЗМЕНЯТЬ эту строку, но могут читать.

2. Exclusive Lock (X-lock) — Исключительная блокировка

-- PostgreSQL
SELECT * FROM users WHERE id = 1 FOR UPDATE;
Транзакция 1: SELECT FOR UPDATE → читает и блокирует (только одна)
Транзакция 2: SELECT FOR UPDATE → ждёт
Транзакция 3: SELECT FOR SHARE  → ждёт (зависит от типа БД)
Транзакция 4: UPDATE            → ждёт

Что блокируется: все остальные транзакции ждут, пока текущая не завершится.

Пример с Java/Hibernate

import org.springframework.data.jpa.repository.Query;
import org.springframework.data.jpa.repository.Lock;
import org.springframework.data.jpa.repository.Modifying;
import javax.persistence.LockModeType;

public interface UserRepository extends JpaRepository<User, Long> {
    
    // Пессимистическая блокировка (WRITE)
    @Lock(LockModeType.PESSIMISTIC_WRITE)
    @Query("SELECT u FROM User u WHERE u.id = ?1")
    User findUserForUpdate(Long id);
    
    // Пессимистическая блокировка (READ)
    @Lock(LockModeType.PESSIMISTIC_READ)
    @Query("SELECT u FROM User u WHERE u.id = ?1")
    User findUserForShare(Long id);
}

Использование:

@Service
public class TransferService {
    @Autowired
    private UserRepository userRepository;
    
    @Transactional
    public void transferMoney(Long fromId, Long toId, BigDecimal amount) {
        // Блокируем обе строки (USER таблица)
        User from = userRepository.findUserForUpdate(fromId);
        User to = userRepository.findUserForUpdate(toId);
        
        // Теперь никто другой не может читать/изменять этих пользователей
        from.setBalance(from.getBalance().subtract(amount));
        to.setBalance(to.getBalance().add(amount));
        
        userRepository.save(from);
        userRepository.save(to);
        // После commit блокировка снимается
    }
}

Сложный пример: Несколько строк

@Service
public class OrderService {
    @Autowired
    private OrderRepository orderRepository;
    
    @Transactional
    public void updateOrder(Long orderId) {
        // Блокируем строку в orders таблице
        Order order = orderRepository.findByIdForUpdate(orderId);
        
        // Блокируем все связанные items
        List<OrderItem> items = itemRepository.findByOrderIdForUpdate(orderId);
        
        for (OrderItem item : items) {
            item.setPrice(item.getPrice() * 1.1); // +10%
        }
        
        order.setStatus("UPDATED");
        orderRepository.save(order);
        itemRepository.saveAll(items);
    }
}

Что блокируется:

  • 1 строка в orders таблице (id = orderId)
  • N строк в order_items таблице (все items этого заказа)

Проблемы пессимистической блокировки

1. Deadlock — Взаимная блокировка

// Транзакция 1
Transaction 1: блокирует User(1)     // Успешно
              блокирует User(2)     // Ждёт

// Транзакция 2
Transaction 2: блокирует User(2)     // Успешно
              блокирует User(1)     // Ждёт (DEADLOCK!)

У вас есть две транзакции, которые ждут друг друга в бесконечном цикле.

Решение:

// ПРАВИЛЬНО: всегда в одном порядке
@Transactional
public void transferMoney(Long id1, Long id2, BigDecimal amount) {
    // Берём меньший ID первым, чтобы избежать deadlock
    Long firstId = Math.min(id1, id2);
    Long secondId = Math.max(id1, id2);
    
    User user1 = userRepository.findUserForUpdate(firstId);
    User user2 = userRepository.findUserForUpdate(secondId);
    // Обработка...
}

2. Низкая параллельность (Poor Concurrency)

Если транзакции долгие, другие потоки долго ждут:

Транзакция 1: [BLOCK 0ms] [WORK 5000ms] [UNBLOCK]
Транзакция 2:            [ожидание 5000ms] [BLOCK] [WORK 5000ms]
Транзакция 3:                             [ожидание 10000ms] ...

Решение: минимизировать время в блокировке

@Transactional
public void fastUpdate(Long id) {
    // ПЛОХО: долгая работа в lock
    User user = userRepository.findUserForUpdate(id);
    expensiveCalculation(); // 5 секунд!
    user.setValue(result);
    userRepository.save(user);
    
    // ХОРОШО: вычисляем до блокировки
    BigDecimal result = expensiveCalculation();
    
    User user = userRepository.findUserForUpdate(id);
    user.setValue(result);
    userRepository.save(user);
}

Таблица типов блокировок

ТипБлокирует чтоДругие могут читатьДругие могут писать
PESSIMISTIC_READСтрокаНет (в Postgres)Нет
PESSIMISTIC_WRITEСтрокаНетНет
PESSIMISTIC_FORCE_INCREMENTСтрока + версияНетНет

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

Пессимистическая:

  • Блокирует сразу при чтении
  • Гарантирует отсутствие конфликтов
  • Медленнее при большом количестве потоков
  • Риск deadlock

Оптимистическая:

  • Не блокирует, использует версионирование
  • Проверяет конфликты только при update
  • Быстрее при низких конфликтах
  • Может быть медленнее при частых конфликтах (retry loop)

Рекомендации

Используй пессимистическую блокировку когда:

  • Конфликты вероятны
  • Операция критична (деньги, инвентарь)
  • Очень важна консистентность

Используй оптимистическую когда:

  • Конфликты редки
  • Нужна высокая параллельность
  • Можно переделать операцию
Что блокируется при пессимистической блокировке | PrepBro