How databases let many transactions run at once without corrupting each other — locks vs MVCC
Databases serve thousands of concurrent transactions. Without concurrency control, two transactions reading and writing the same row simultaneously corrupt data. The mechanism the database uses — locking or MVCC — determines how much contention your application experiences, whether reads block writes, and how deadlocks arise and are resolved.
Pessimistic locking: assume conflict will happen — acquire a lock before reading or writing, hold it until commit. Prevents conflicts but limits concurrency. MVCC (Multi-Version Concurrency Control): keep multiple versions of each row. Readers see a consistent snapshot from the moment their transaction started; writers create a new version. Readers never block writers and writers never block readers.
PostgreSQL and MySQL InnoDB use MVCC. It's why a long-running read transaction can see a consistent view of data even while other transactions are committing changes.
Deadlocks are not bugs — they're an expected outcome of concurrent locking and the database handles them. The aborted transaction should be retried by the application. To minimise deadlocks, acquire locks in a consistent order and keep transactions short.
MVCC's downside is table bloat — old versions accumulate until VACUUM cleans them up. A long-running transaction holds back the "horizon" — preventing VACUUM from reclaiming old versions needed by that transaction. This is the primary cause of table bloat in Postgres.