Django select_for_update приобретает блокировку отношения

У меня есть пример кода, который должен принимать блокировку строки (кортежа) в postgres, однако он, похоже, принимает блокировку таблицы (отношения) вместо этого:

with transaction.Atomic(savepoint=True, durable=False):
    record = MyModel.objects.select_for_update().filter(pk='1234')
    record.delete()
    time.sleep(5)
    raise Exception

Просматривая pg_locks во время транзакции, я вижу:

select locktype, database, relation::regclass, pid, mode, granted from pg_locks where pid <> pg_backend_pid();

enter image description here

По моим знаниям, я должен был увидеть "tuple" в типе блокировки, поскольку я блокирую только конкретную строку/ы, а не всю таблицу

Первые дела

Вы фактически не выполнили SELECT FOR UPDATE запрос.

  • record = MyModel.objects.select_for_update().filter(pk='1234') возвращает QuerySet, запрос не выполняется.
    • record.delete() выполняет только DELETE запрос.
  • Запрос SELECT FOR UPDATE получил бы relation RowShareLock.
    • Вы можете проверить это, выполнив QuerySet с .first(), т.е. record = MyModel.objects.select_for_update().filter(pk='1234').first().
  • Вы можете проверить это с помощью log all sql queries.

Блокировки кортежей

Блокировка уровня строки FOR UPDATE получена, но не показана в вашем представлении pg_locks (она не показана и на моем). Вместо этого мы видим transactionid ExclusiveLockvirtualxid ExclusiveLock).

From https://www.postgresql.org/docs/9.3/view-pg-locks.html:

Хотя кортежи являются блокируемым типом объекта, информация о блокировках на уровне строки хранится на диске, а не в памяти, и поэтому блокировки на уровне строки обычно не появляются в этом представлении. Если транзакция ожидает блокировки на уровне строки, она обычно отображается в представлении как ожидающая постоянного идентификатора транзакции текущего владельца этой блокировки строки.

From https://www.postgresql.org/docs/9.4/explicit-locking.html:

FOR UPDATE

...

Режим блокировки FOR UPDATE также приобретается любым DELETE в ряду ...

Вы можете проверить это эмпирически, выполнив в терминале psql:

  • перед record.delete()
    • SELECT FROM mymodel WHERE id='1' FOR UPDATE; работает.
    • SELECT FROM mymodel WHERE id='1234' FOR UPDATE; работает.
  • после record.delete()
    • SELECT FROM mymodel WHERE id='1' FOR UPDATE; работает.
    • SELECT FROM mymodel WHERE id='1234' FOR UPDATE; не работает.

Блокировки отношений

  1. Судя по всему, relation AccessShareLock получен для SELECT запроса, который вы не показали в примере кода, например, MyModel.objects.filter(pk='1234').first().
  2. relation RowExclusiveLock приобретается для DELETE запроса.

From https://www.postgresql.org/docs/9.4/explicit-locking.html:

ACCESS SHARE

Конфликтует с ...

Команда SELECT приобретает блокировку этого режима на ссылающихся таблицах. В общем, любой запрос, который только читает таблицу и не изменяет ее, приобретает этот режим блокировки.

ROW EXCLUSIVE

Конфликты с ...

Команды UPDATE, DELETE и INSERT приобретают этот режим блокировки на целевой таблице (в дополнение к ACCESS SHARE блокировкам на любых других ссылающихся таблицах). В общем, этот режим блокировки будет приобретен любой командой, изменяющей данные в таблице.

Вернуться на верх