Что блокируется при пессимистической блокировке
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Что блокируется при пессимистической блокировке
Пессимистическая блокировка (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)
Рекомендации
Используй пессимистическую блокировку когда:
- Конфликты вероятны
- Операция критична (деньги, инвентарь)
- Очень важна консистентность
Используй оптимистическую когда:
- Конфликты редки
- Нужна высокая параллельность
- Можно переделать операцию