What rules should I follow to prevent database deadlocks?
Question
Two transactions run concurrently. A locked X in Table1 and wants to update Y in Table2; at the same time B locked Y in Table2 and wants to update X in Table1. They wait on each other forever. When designing DB operations in application code, what rules (lock ordering, short transactions, etc.) should I follow to minimize deadlock risk?
Answer
Short answer: what you’re hitting is a classic lock-ordering cycle: A locks X then Y, B locks Y then X. Break the cycle and you solve the deadlock at the root.
The real issue is this: a deadlock isn’t bad luck, it’s the mathematical consequence of inconsistent lock ordering. When two transactions request locks in different orders, a cycle forms and each waits on the other.
- Always acquire locks in the same global order. This is the most important rule. Lock rows in the same deterministic order everywhere — for example, always by ascending primary key. If both transactions lock X before Y, a cycle can never form; one waits for the other and finishes.
- Keep the transaction short and narrow. Move slow/external work (API calls, file writes, waiting on the user) outside the transaction. Acquire the lock as late as possible and hold it as briefly as possible. The longer a transaction stays open, the larger the collision window grows.
- Touch the fewest rows, lock targeted. Instead of broad locks, apply
SELECT ... FOR UPDATEonly to the row you need. Locking more rows than necessary widens the collision surface. - Use a single statement where possible. Where you can, do the work in a single
UPDATE/INSERT ... ON CONFLICT; then the DB manages the lock ordering for you and you remove the risk of forming a cycle in application code. - Accept that deadlocks will happen anyway, and retry. Despite every precaution, occasional deadlocks occur; the DB picks one transaction as the “victim” and aborts it. Treat this not as an error but as an expected condition: make the operation idempotent and retry with backoff.
Bottom line: I’d set up the trio of consistent lock order + short transactions + retry-on-deadlock. A common mistake: trying to solve deadlocks by raising the isolation level. That usually doesn’t help and instead adds more locks. Break the cycle, don’t force the isolation level.
Comments
Sign in with your GitHub account to join the discussion. Comments are stored in GitHub Discussions.