Transactions, MVCC, and Concurrency
Understand ACID, transaction boundaries, isolation levels, locks, and safe concurrent updates.
Transactions and ACID
A transaction groups statements into one logical unit. ACID refers to atomicity, consistency, isolation, and durability. BEGIN starts a transaction, COMMIT makes its changes permanent, and ROLLBACK discards uncommitted changes. Transactions are crucial when multiple writes must succeed together, such as creating an order and reducing inventory.
Keep transactions appropriately short. Long-running transactions can retain old row versions, increase contention, and interfere with maintenance. Never assume that two separate queries are atomic together unless they are in the same transaction or protected by another suitable mechanism.
BEGIN;
UPDATE accounts SET balance = balance - 500 WHERE id = 1;
UPDATE accounts SET balance = balance + 500 WHERE id = 2;
COMMIT;- Transactions group related changes.
- ACID describes key transaction guarantees.
- Keep transactions short and handle failures deliberately.
Which command discards uncommitted changes in the current transaction?
MVCC and Isolation Levels
PostgreSQL uses multiversion concurrency control (MVCC) so statements can read consistent row versions while concurrent transactions modify data. Readers and writers often avoid blocking each other, though locks and conflicts still occur. Isolation levels determine which concurrency anomalies may be observed.
READ COMMITTED is PostgreSQL's default and gives each statement a snapshot as of that statement's start. REPEATABLE READ uses a stable transaction snapshot but can still require retry handling in some scenarios. SERIALIZABLE provides stronger guarantees by detecting executions that cannot be serialized, and transactions may need to be retried after serialization failures.
BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SELECT balance FROM accounts WHERE id = 1;
-- perform related reads and writes
COMMIT;- MVCC provides snapshots of row versions.
- READ COMMITTED is PostgreSQL's default isolation level.
- Stronger isolation can require retrying transactions.
What is PostgreSQL's default transaction isolation level?
Locks, Row-Level Locking, and Deadlocks
Locks coordinate conflicting operations. SELECT ... FOR UPDATE locks selected rows so competing transactions cannot modify them in conflicting ways until the transaction ends. This is useful for workflows that read a row and then make a decision based on it. Use row locks carefully and keep the transaction short.
A deadlock occurs when transactions wait on each other's locks in a cycle. PostgreSQL detects deadlocks and aborts one transaction. Applications should handle the error and may retry appropriate transactions. Consistent lock ordering can reduce deadlock risk, but retry logic is still valuable.
BEGIN;
SELECT quantity
FROM inventory
WHERE product_id = 42
FOR UPDATE;
UPDATE inventory
SET quantity = quantity - 1
WHERE product_id = 42 AND quantity > 0;
COMMIT;- FOR UPDATE locks selected rows for conflicting operations.
- Deadlocks can cause PostgreSQL to abort one transaction.
- Use consistent lock ordering and bounded retry logic where appropriate.
What can PostgreSQL do when it detects a deadlock?