Error 1213 in the logs, and the chat lights up: the database is broken. It is not. Two transactions locked rows in opposite order, A waits for B, B waits for A, and InnoDB did the only sane thing: picked a victim and killed it. The cycle is gone. This is a feature.

The classic shape is a money transfer. One request moves funds from account 1 to account 2, another from 2 to 1, both update the first account and then the second. Opposite order, instant cycle under load. Tests never show it, because tests do not run two of these in the same millisecond.

The application has one duty here: know that this error is retryable. MySQL says 1213, PostgreSQL says SQLSTATE 40001. It does not mean “your data is wrong”. It means “try again, you will probably win this time”. So catch it, roll back, wait a random 10 to 50 ms, run the whole transaction again. Bounded, three attempts, then give up loudly. The jitter matters. Two retries on a fixed delay collide again, like two people stepping aside in the same direction.

The detail people get wrong: retry the whole transaction, from the first read. The victim is rolled back completely, and the values you read before the deadlock may already be stale. A retry that reuses old values from memory is a quiet corruption machine.

Prevention is one sentence. Lock rows in a consistent order everywhere. For the transfer, sort the two account ids and update the smaller one first. Cycles need opposite orders. One global order, no cycles.

Retry handles the rare case. Ordering makes it rare. You want both. But if the deadlock graph in SHOW ENGINE INNODB STATUS shows the same pair of queries every day, stop retrying and fix the order. Ours showed the same pair for weeks before anyone opened the graph.