ACID does not encode every business invariant

ACID describes atomicity, consistency, isolation, and durability. Consistency does not mean the database infers every business rule; constraints and transaction design must express those rules. Isolation-level names also do not guarantee identical behavior across engines.

A dirty read observes an uncommitted value; a non-repeatable read returns a changed value for a row read earlier; a phantom changes the set of rows matching a predicate. These are not the only concern: successfully committed transactions can still produce a result that is inconsistent with every serial ordering, a serialization anomaly.

Compare the engine’s read rules

Setting/engineSnapshot for ordinary readsImportant caveatWhat to verify
PostgreSQL Read Committed (default)A snapshot at each statement startTwo SELECTs in one transaction can see different committed statesDo not treat a multi-statement check as one stable snapshot
PostgreSQL Repeatable ReadTransaction snapshot from the first ordinary statementPrevents dirty, non-repeatable, and phantom reads, but serialization anomalies remain; conflicting updates can abortCheck write-skew exposure and whole-transaction retry handling
PostgreSQL SerializableConflict monitoring to ensure committed results are serial-equivalentA transaction may fail with a serialization error rather than waitCan the application retry the whole transaction on SQLSTATE 40001?
MySQL InnoDB Repeatable Read (default)Ordinary consistent reads use a snapshot established by the first readLocking reads can see a different state; next-key/gap locks may block inserts depending on predicate and indexCheck SELECT ... FOR UPDATE/SHARE and the scanned index range

PostgreSQL maps requested Read Uncommitted to Read Committed. Its Repeatable Read is stronger than the standard minimum because it prevents phantoms, but it does not prevent every serialization anomaly. InnoDB ordinary SELECTs are consistent snapshot reads; locking reads and UPDATE/DELETE follow separate lock rules. Do not generalize this as “reads never block writes.”

Hypothetical example: two on-call staff both sign off

Assume two staff are on call and at least one must remain active. Transactions A and B each read that two people are active, then deactivate a different staff row. Because each changes a different row, both may commit while leaving zero active staff under snapshot-based Repeatable Read. This illustrates possible write skew; it is not a claim that every engine and configuration behaves this way.

To protect the invariant, verify whether Serializable plus whole-transaction retry is suitable for the actual engine/version, or consider a shared guard row or constraint design. Do not infer safety merely because each transaction updates one row.

A practical review sequence

  1. Record the product, storage engine, version, and configured isolation level.
  2. Identify ordinary versus locking reads and whether snapshots are statement- or transaction-scoped.
  3. Express the invariant as a concurrent schedule and classify the risk: lost update, write skew, or phantom.
  4. Choose a constraint, lock, or stronger level, then retry the whole transaction on deadlock or serialization failure.
  5. Reproduce concurrent sessions in a test environment and measure latency, errors, and retry cost.

The scenario is hypothetical and was not run against a database. Use the versioned vendor documentation below for implementation details.

Sources: PostgreSQL 18 Transaction Isolation, MySQL 8.4 InnoDB Transaction Isolation, MySQL 8.4 Consistent Nonlocking Reads, MySQL 8.4 Locking Reads.