Two transactions each hold locks the other needs; InnoDB kills one (the victim). Deadlocks are normal in busy systems — read the LATEST DETECTED DEADLOCK trace and fix the access pattern, don't just retry-blind.
Transaction A updates rows 1→2 while B updates 2→1. Same statements, same data — opposite order: textbook deadlock. Fix: consistent ordering (e.g. always by ascending primary key) or fewer rows per transaction.
Inserts/updates touching the same secondary-index range hold gap locks that collide. Batched updates in one big transaction amplify this.
mysql -e "SHOW ENGINE INNODB STATUS\G" | sed -n '/LATEST DETECTED DEADLOCK/,/^---/p' | head -40
# the trace shows both transactions' last statements + lock waits — map them to your code paths
# sort target ids before updating: UPDATE ... WHERE id IN (...) ORDER BY id, or loop in ascending-id order in every path
# commit often (shorter lock windows) + code-level retry on ER_LOCK_DEADLOCK for idempotent writes
Retry-on-deadlock is required regardless — a surviving victim's transaction is rolled back. But retries without fixing order just reshuffle who dies. innodb_lock_wait_timeout is a different error (1205, lock wait timeout): deadlocks are detected instantly; long waits are not deadlocks.
In a busy write-heavy system they're occasional and expected — InnoDB detects them and rolls one side back. Frequent deadlocks indicate an access-pattern problem (inconsistent ordering, giant transactions), not corruption.
It races whatever else touches those rows: the interleaving matters, so timing decides. Fix the pattern (order + shorter transactions) rather than chasing the specific run.
Our most-documented failures, packaged as ready-to-ship starter kits: Docker, Kubernetes, and Terraform.
Browse the template store →One-time. Yours to modify. Instant download from the NinjaOps template store.