PostgreSQL Row Locks in Formance
Last updated: July 24, 2025
In Formance, every transaction involves writing changes to the accounts_volumes table. Even if your account has overdraft enabled (i.e., no balance check), PostgreSQL still applies row-level locks when updating balances.
Let’s break it down:
overdraft not allowed
Formance proactively issues:
SELECT * FROM accounts_volumes WHERE account = 'xyz' FOR UPDATE;This does two things:
Explicitly locks the row before doing any validation.
Ensures other transactions trying to read or lock the same row are blocked at that point.
overdraft enabled
When a transaction updates the volume of an account, the SQL statement typically looks like:
UPDATE accounts_volumes SET ... WHERE account = 'xyz';During the
UPDATE, PostgreSQL:Locates the row matching
account = 'xyz'.Acquires a row-level exclusive lock on that row. PostgreSQL uses this lock to prevent dirty writes and ensure ACID compliance—even if no explicit
SELECT ... FOR UPDATEis issued.Writes a new version of the row (PostgreSQL is MVCC-based).
The row-level lock acquired by PostgreSQL during an UPDATE lasts for the entire duration of the SQL transaction, not just during the execution of the UPDATE statement itself. This lock is held until the transaction is COMMIT or ROLLBACK.
So to clarify:
Use Case | Locking Mechanism |
Overdraft allowed (e.g., | Lock occurs implicitly during |
Overdraft not allowed | Lock occurs via |
In both cases, concurrent writes to the same account row create contention, they become serialized — they have to wait for the lock to be released.
It's especially easy to fall into this trap with world account, since it's often used as a universal source.