Concurrency & Locking
Row-level vs table-level locks
Lock granularity | What it protects | Concurrency impact |
|---|---|---|
Row-level | A single row being read or modified. | High — other transactions can freely work with different rows in the same table. |
Table-level | The entire table, e.g. during | Low — every other transaction touching the table has to wait. |
INSERT/UPDATE/DELETE statements, which is why thousands of transactions can update different rows of the same table simultaneously. Table-level locks are reserved for operations that genuinely need exclusive control of the whole table, such as schema changes.Shared vs exclusive locks
Lock type | Who else can acquire it | Typical trigger |
|---|---|---|
Shared (read) lock | Other shared locks — many readers can hold one at once. | A plain |
Exclusive (write) lock | Nobody — any other lock request has to wait. |
|
The rule of thumb is: readers don't block readers, but a writer blocks everyone else from touching that same row until it commits or rolls back.
Deadlocks
A deadlock happens when two transactions each hold a lock the other one needs, and each is waiting for the other to release it — neither can ever proceed. A classic example: transaction A locks row 1 and then tries to lock row 2, while transaction B has already locked row 2 and is now trying to lock row 1. Both wait forever, unless something intervenes.
Two transactions that can deadlock
-- Transaction A BEGIN; UPDATE accounts SET balance = balance - 50 WHERE id = 1; -- ... pauses ... UPDATE accounts SET balance = balance + 50 WHERE id = 2; COMMIT; -- Transaction B, running at the same time BEGIN; UPDATE accounts SET balance = balance - 20 WHERE id = 2; -- ... pauses ... UPDATE accounts SET balance = balance + 20 WHERE id = 1; COMMIT;
If A locks row 1 and B locks row 2 before either reaches its second statement, each is now waiting on a lock the other holds. Databases detect this situation automatically: one of the two transactions is picked as the "victim," its statement fails with a deadlock error, and its transaction is rolled back so the other can proceed. The application is expected to catch that error and retry.
Preventing race conditions with SELECT ... FOR UPDATE
SELECT ... FOR UPDATE closes this gap by taking an exclusive lock on the rows as you read them, so no other transaction can modify (or also lock) them until you commit or roll back.Safely decrementing stock
BEGIN; -- lock the row for the duration of this transaction SELECT quantity FROM inventory WHERE product_id = 7 FOR UPDATE; -- application checks quantity here; suppose it is 3 and the order needs 2 UPDATE inventory SET quantity = quantity - 2 WHERE product_id = 7; COMMIT;
SELECT ... FOR UPDATE the same row will simply wait until this one commits, at which point it sees the updated quantity — guaranteeing two concurrent orders can never both oversell the same stock.