Что будет, если транзакция попытается залочить запись, залоченную другой транзакцией
Если транзакция попытается залочить запись, которая уже была залочена другой транзакцией, то поведение зависит от типа блокировки, настроек СУБД, а также от того, как долго продолжается блокировка и какие действия предпринимает система. В большинстве случаев блокирующийся запрос будет ожидать освобождения ресурса. Ниже рассмотрим этот процесс максимально подробно, на примере реляционных баз данных (например, PostgreSQL, MySQL, Oracle).
🔒 Что такое блокировка (lock) записи?
Блокировка записи означает, что СУБД предотвращает другим транзакциям изменение или чтение определённых строк таблицы до завершения текущей транзакции. Это делается для обеспечения согласованности данных в многопользовательской среде.
🔁 Поведение при попытке залочить уже заблокированную запись
✅ Ожидание блокировки (Lock Wait)
Когда транзакция T2 пытается получить блокировку строки, которая уже заблокирована транзакцией T1, СУБД помещает T2 в очередь ожидания. Она будет ждать:
-
Пока T1 не выполнит COMMIT или ROLLBACK
-
Или пока не истечёт таймаут ожидания (lock_timeout, innodb_lock_wait_timeout и др.)
Пример на PostgreSQL:
\-- Транзакция 1
BEGIN;
SELECT \* FROM accounts WHERE id = 1 FOR UPDATE;
\-- блокировка строки id=1
\-- Транзакция 2 (в другом сеансе)
BEGIN;
SELECT \* FROM accounts WHERE id = 1 FOR UPDATE;
\-- будет ждать завершения Транзакции 1
⏱ Таймаут блокировки
Если в течение определённого времени транзакция не может получить блокировку, будет выброшено исключение.
В PostgreSQL:
SET lock_timeout = '5s';
Если блокировка не освобождается в течение 5 секунд, запрос завершится с ошибкой:
ERROR: canceling statement due to lock timeout
В MySQL:
SET innodb_lock_wait_timeout = 10;
Если ожидание длится дольше 10 секунд:
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
🧠 Почему важно понимать поведение блокировок
Если не учитывать конкуренцию за ресурсы, возможно:
-
Падение производительности: транзакции «зависают» в ожидании
-
**Появление взаимных блокировок (deadlock)
**
💥 Deadlock (взаимная блокировка)
Если две или более транзакции блокируют друг друга, возникает «взаимная блокировка». СУБД обнаруживает её и отменяет одну из транзакций, чтобы разорвать цикл.
Пример:
-
T1 блокирует строку A и хочет заблокировать строку B
-
T2 блокирует строку B и хочет заблокировать строку A
Это приводит к взаимному ожиданию, и одна из транзакций будет аварийно завершена:
ERROR: deadlock detected
DETAIL: Process 123 waits for ShareLock on transaction 456; blocked by process 789.
🔐 Типы блокировок (на примере PostgreSQL)
-
**Row-level lock:
**-
SELECT ... FOR UPDATE блокирует строки для записи
-
SELECT ... FOR SHARE позволяет чтение, но блокирует запись
-
-
**Table-level lock:
**- LOCK TABLE блокирует всю таблицу (например, для DDL операций)
-
**Advisory Lock (логическая блокировка):
**- Блокировка по произвольному ключу (например, pg_advisory_lock)
🚦 Стратегии борьбы с конфликтами блокировок
-
**Минимизировать время удержания блокировок:
**-
Делать меньше работы внутри транзакции
-
Избегать долгих вычислений между BEGIN и COMMIT
-
-
**Последовательный порядок доступа к ресурсам:
**- Все транзакции должны блокировать ресурсы в одинаковом порядке
-
**Установка таймаутов:
**- Настроить ограничения ожидания блокировки
-
**Обработка ошибок блокировок:
**- Автоматическая повторная попытка транзакции при deadlock
-
**Оптимистичная блокировка:
**- Проверка изменений по version полю (например, lock_version)
🔄 Жизненный цикл при блокировке строки
- **Транзакция A начинает
** -
Делает SELECT ... FOR UPDATE по строке X
-
Система блокирует строку X
-
Транзакция B делает SELECT ... FOR UPDATE по X
-
B ждёт завершения A
-
Если A завершает COMMIT, то B получает блокировку
-
Если A долго не завершает, B либо ждёт, либо падает по таймауту
📊 Визуализация ожидания (в PostgreSQL):
SELECT blocked_locks.pid AS blocked_pid,
blocking_locks.pid AS blocking_pid,
blocked_activity.query AS blocked_query,
blocking_activity.query AS blocking_query
FROM pg_catalog.pg_locks blocked_locks
JOIN pg_catalog.pg_stat_activity blocked_activity
ON blocked_activity.pid = blocked_locks.pid
JOIN pg_catalog.pg_locks blocking_locks
ON blocking_locks.locktype = blocked_locks.locktype
AND blocking_locks.DATABASE IS NOT DISTINCT FROM blocked_locks.DATABASE
AND blocking_locks.relation IS NOT DISTINCT FROM blocked_locks.relation
AND blocking_locks.page IS NOT DISTINCT FROM blocked_locks.page
AND blocking_locks.tuple IS NOT DISTINCT FROM blocked_locks.tuple
AND blocking_locks.transactionid IS NOT DISTINCT FROM blocked_locks.transactionid
AND blocking_locks.classid IS NOT DISTINCT FROM blocked_locks.classid
AND blocking_locks.objid IS NOT DISTINCT FROM blocked_locks.objid
AND blocking_locks.objsubid IS NOT DISTINCT FROM blocked_locks.objsubid
AND blocking_locks.pid != blocked_locks.pid
JOIN pg_catalog.pg_stat_activity blocking_activity
ON blocking_activity.pid = blocking_locks.pid;
Если транзакция пытается залочить уже залоченную запись, она будет ожидать освобождения блокировки или получит исключение по таймауту или deadlock, в зависимости от ситуации. Управление блокировками — ключевой аспект при проектировании конкурентных и отказоустойчивых систем.