Ответ
Да, сталкивался. Классический случай — взаимная блокировка (deadlock) при конкурентном обновлении связанных записей в неправильном порядке.
Пример сценария deadlock в PostgreSQL:
-- Транзакция 1: Пользователь A переводит деньги пользователю B
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE user_id = 1; -- Блокирует строку user_id=1
-- Транзакция 2 (параллельно): Пользователь B переводит деньги пользователю A
BEGIN;
UPDATE accounts SET balance = balance - 50 WHERE user_id = 2; -- Блокирует строку user_id=2
-- Теперь deadlock:
-- Транзакция 1 пытается заблокировать строку user_id=2, но она уже заблокирована Транзакцией 2.
UPDATE accounts SET balance = balance + 100 WHERE user_id = 2; -- Транзакция 1 ЖДЕТ
-- Транзакция 2 пытается заблокировать строку user_id=1, но она уже заблокирована Транзакцией 1.
UPDATE accounts SET balance = balance + 50 WHERE user_id = 1; -- Транзакция 2 ЖДЕТ -> DEADLOCK
СУБД обнаружит deadlock и принудительно прервет одну из транзакций (в PostgreSQL с ошибкой SQLSTATE 40P01).
Как мы решали эту проблему:
- Установка строгого порядка блокировки записей. Всегда обновляли счета в порядке возрастания
user_id. Это гарантировало, что все транзакции запрашивают блокировки в одной последовательности, исключая circular wait. - Сокращение времени транзакции. Выносили логику валидации и вычислений за пределы транзакции, оставляя внутри только минимальные
UPDATE/INSERT. - Использование
SELECT ... FOR UPDATE SKIP LOCKEDдля обработки очередей задач, что позволяло избегать блокировок при конкурентном чтении. - Настройка таймаута для запросов (
lock_timeoutв PostgreSQL), чтобы долгая блокировка не подвешивала приложение, а вызывала контролируемую ошибку, которую можно было обработать (например, повторить запрос).