Created at: 2026-01-14
Despite Postgres documentation having _some_ information on row locks, you usually can't see them in the catalog table pg_locks:
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 process 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.
As per the above, row-level lock is stored on disk. This information is stored in the xmax field - which serves two purposes:
What happens if both need to be stored at the same time? Well, This never happens. A tuple can't be deleted and locked at the same time.
The advantage of storing these locks on disk is that the pg_locks underlying table won't go past its size limit. But at the same time they increase I/O load since we are effectivaly dirtying the buffer for this row and it needs to be written to disk during the next checkpoint. More information, including what happens when multiple locks on the same row
| | FOR KEY SHARE | FOR SHARE | FOR NO KEY UPDATE | FOR UPDATE |
|-------------------|---------------|-----------|-------------------|------------|
| FOR KEY SHARE | | | | X |
| FOR SHARE | | | X | X |
| FOR NO KEY UPDATE | | X | X | X |
| FOR UPDATE | X | X | X | X |
Row-level locks do not affect data querying; they block only writers and
lockers to the same row. Row-level locks are released at transaction end or
during savepoint rollback, just like table-level locks.
Most people use SELECT ... FOR UPDATE but it can cause problems. This lock should only be taken when you are DELETING the row, or updating a unique key column or a primary key column (basically a column that can be used as FK on another table). In other words, an UPDATE statement that modifies a column that is part of a non-partial non-expression unique index.
If that is not the case, use SELECT ... FOR NO KEY UPDATE. This ensures you are not blocking inserts into other tables that refer the table being updated. More info
Avoid using this against a row in a table referenced by a foreign key as you will block all INSERTs from other tables that also reference this row. More info
Any time when you are updating a non-primary key column and a non-unique key column.
I have never seen a usecase for this.
In fact, Postgres uses this level of lock internally. Whenever an INSERT happens in a table with a foreign key, this lock will be taken in the referenced table.
This prevents a race condition in which another transaction might delete the referenced row before the INSERT finishes.
I have never seen a usecase for this. Postgres also does not use this lock internally.