Postgres locks
October 12, 2024
Concurrent operations are a must-have for databases used to power rich, multi-user applications. But in order to support concurrency, a database has to handle the possibility of two users accessing or editing the same thing at the same time.
In Postgres, simultaneous reads and writes are made possible by a rich set of locks, which can get quite complicated. Your average SaaS app won't have to worry about locks, but they can quickly become a pain point when dealing with complicated transactions.
Postgres locks control access at two levels: tables and rows. The majority of Postgres commands automatically acquire a lock that corresponds with their semantics. It's also possible to enable/disable locks manually via the LOCK command.
For table locks, the terminology is a bit confusing because some of them include the name "row". (Postgres's lock nomenclature is descended from Oracle's.) As can be seen in the following table, a lock is defined by how it conflicts with other locks.
| Requested Lock Mode | ACCESS SHARE | ROW SHARE | ROW EXCL. | SHARE UPDATE EXCL. | SHARE | SHARE ROW EXCL. | EXCL. | ACCESS EXCL. |
|---|---|---|---|---|---|---|---|---|
| ACCESS SHARE | X | |||||||
| ROW SHARE | X | X | ||||||
| ROW EXCLUSIVE | X | X | X | X | ||||
| SHARE UPDATE EXCLUSIVE | X | X | X | X | X | |||
| SHARE | X | X | X | X | X | |||
| SHARE ROW EXCLUSIVE | X | X | X | X | X | X | ||
| EXCLUSIVE | X | X | X | X | X | X | X | |
| ACCESS EXCLUSIVE | X | X | X | X | X | X | X | X |
A useful way to understand Postgres's system of table locks is to walk backwards from the most restrictive type and view them in pairs.
- An
ACCESS EXCLUSIVElock conflicts with every other type. It's only acquired on heavy operations such asDROP TABLE,REINDEX, andVACUUM FULL. If you use aLOCK TABLEstatement, it's the default. - An
EXCLUSIVElock is a slightly less prohibitive version that allows concurrent reads via theACCESS SHARElock, which is used for read-only commands likeSELECT. This lock is used byREFRESH MATERIALIZED VIEW CONCURRENTLY. - A
SHARE ROW EXCLUSIVElock enables another corresponding operation:ROW SHARE, which is acquired forSELECT FOR UPDATEandFOR SHARE. It conflicts with itself, so you can only have one of these pending on a table at a time. It's acquired byCREATE TRIGGERand certainALTER TABLEexpressions. So you can update a table while creating a trigger on it, but you can't alter a table's triggers via multiple operations at the same time. - A
SHARElock is the same thing as aSHARE ROW EXCLUSIVEone except that it doesn't conflict with itself. This is used forCREATE INDEXstatements, since you should be able to create multiple indexes at the same time. - A
SHARE UPDATE EXCLUSIVErelaxes the constraint against concurrentROW EXCLUSIVElocks, which activate with your standard CRUD operations:UPDATE,DELETE,INSERT, andMERGE.SHARE UPDATE EXCLUSIVEis acquired onANALYZE,CREATE INDEX CONCURRENTLY, andCOMMENT ON.
What about row-level locks?
| Requested Lock Mode | 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 locks are much simpler and have a couple of other nice guarantees. Unlike table locks, row locks only concern writes, so they do not block concurrent queries. And a single transaction can obtain multiple locks on the same row if needed.
If we go backwards again, we have:
FOR UPDATE, which is used byDELETEand a few special cases ofUPDATE.FOR NO KEY UPDATEis acquired by the rest ofUPDATEoperations and lets you runFOR KEY SHAREat the same time.FOR KEY SHAREis only acquired explicitly. The "FOR NO KEY" part can be understood as meaning that unlikeFOR UPDATE, the row's key is not modified.FOR SHAREis another explicit-only lock.
So really the takeaway for rows is that you can't update the same row via multiple pending transactions. Trying to do so can trigger an infamous deadlock where two transactions try to update the same row, but neither can complete because the other blocks it from doing so.