I have this code sample that should take row (tuple) lock in postgres, however it seems to take table (relation) lock instead:
with transaction.Atomic(savepoint=True, durable=False):
record = MyModel.objects.select_for_update().filter(pk='1234')
record.delete()
time.sleep(5)
raise Exception
By looking at the pg_locks during the time of the transaction I can see:
select locktype, database, relation::regclass, pid, mode, granted from pg_locks where pid <> pg_backend_pid();

To my knowledge, I should have seen "tuple" in the locktype since I'm only locking specific row/s and not the entire table
You actually did not perform a SELECT FOR UPDATE query.
record = MyModel.objects.select_for_update().filter(pk='1234') returns a QuerySet, no query is executed.
record.delete() only executes a DELETE command.SELECT FOR UPDATE query would have acquired a relation RowShareLock.
QuerySet with .first(), i.e. record = MyModel.objects.select_for_update().filter(pk='1234').first().A row-level FOR UPDATE lock is acquired but not shown in your pg_locks view (it doesn't show on mine too). Instead, we see transactionid ExclusiveLock (and virtualxid ExclusiveLock).
From https://www.postgresql.org/docs/9.3/view-pg-locks.html:
Although tuples are a lockable type of object, information about row-level locks is stored on disk, not in memory, and therefore row-level locks normally do not appear in this view. If a transaction is waiting for a row-level lock, it will usually appear in the view as waiting for the permanent transaction ID of the current holder of that row lock.
From https://www.postgresql.org/docs/9.4/explicit-locking.html:
FOR UPDATE...
The
FOR UPDATElock mode is also acquired by anyDELETEon a row ...
You can verify this empirically by running in your psql terminal:
record.delete()
SELECT FROM mymodel WHERE id='1' FOR UPDATE; works.SELECT FROM mymodel WHERE id='1234' FOR UPDATE; works.record.delete()
SELECT FROM mymodel WHERE id='1' FOR UPDATE; works.SELECT FROM mymodel WHERE id='1234' FOR UPDATE; doesn't work.relation AccessShareLock appears to be acquired for a SELECT query that you did not show in your code sample, e.g. MyModel.objects.filter(pk='1234').first().relation RowExclusiveLock is acquired for the DELETE command.While these are table-level locks, they only conflict with EXCLUSIVE and/or ACCESS EXCLUSIVE locks, which are not acquired by most other DQL (data query language) and DML (data manipulation language) commands.
From https://www.postgresql.org/docs/9.4/explicit-locking.html:
ACCESS SHAREConflicts with the
ACCESS EXCLUSIVElock mode only.The
SELECTcommand acquires a lock of this mode on referenced tables. In general, any query that only reads a table and does not modify it will acquire this lock mode.
ROW EXCLUSIVEConflicts with the
EXCLUSIVEandACCESS EXCLUSIVElock modes.The commands
UPDATE,DELETE, andINSERTacquire this lock mode on the target table (in addition toACCESS SHARElocks on any other referenced tables). In general, this lock mode will be acquired by any command that modifies data in a table.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With