Deadlock Log Analyzer
A deadlock report tells you everything you need, but in a form that is hard to read: raw InnoDB lock lines with hex record dumps, or PostgreSQL lines like Process 169 waits for ShareLock on transaction 54642. Paste the LATEST DETECTED DEADLOCK section of SHOW ENGINE INNODB STATUS, MySQL error log entries written with innodb_print_all_deadlocks (with or without the *** header lines), or the ERROR: deadlock detected message and DETAIL lines from a PostgreSQL server log or client. The analyzer lists each transaction with its statement, the locks it held and the lock it waited for, decodes record values such as hex 80000005 to 5, and marks gap, next-key, insert intention and record-only locks. It draws the wait-for cycle, names the victim and recognizes the usual causes: rows updated in opposite order, check-then-insert races on gap locks, duplicate-key checks on unique indexes and table-level locks. For each cause it gives concrete fixes and the queries to investigate further. Logs with several deadlocks are split and analyzed one by one. The parser was built against real deadlocks produced on MySQL 8.4 and PostgreSQL 18, and the text you paste stays in your browser.
Do more than deadlock log analyzer — meet Chat2DB
Chat2DB is an AI-powered SQL client for Windows, macOS and Linux. Write SQL in natural language, format and optimize queries automatically, and manage MySQL, PostgreSQL, Oracle and 20+ other databases in one workspace.
How to use
- Copy the deadlock text: in MySQL run SHOW ENGINE INNODB STATUS and take the LATEST DETECTED DEADLOCK section, or copy the error log lines; in PostgreSQL copy the ERROR: deadlock detected entry with its DETAIL lines from the server log.
- Paste it into the box (or load an example) and click Analyze.
- Read the transactions, the cycle and the diagnosis, change the code that takes the locks, and keep a retry for SQLSTATE 40001 / 40P01 in the application.
Frequently asked questions
How do I read LATEST DETECTED DEADLOCK in SHOW ENGINE INNODB STATUS?
The section lists each transaction under *** (1) TRANSACTION, *** (2) TRANSACTION and so on, with the statement it was running. HOLDS THE LOCK(S) shows locks it already had, WAITING FOR THIS LOCK TO BE GRANTED shows the lock it was stuck on, and WE ROLL BACK TRANSACTION (n) names the victim. In each lock line, index and table tell you where the lock is; lock_mode X or lock mode S is the mode; locks rec but not gap is a plain row lock; locks gap before rec is a gap lock; insert intention marks an INSERT waiting to enter a gap; no suffix means a next-key lock. The record dump shows key values in hex, where 80000005 is the signed integer 5. Only the current statement of each transaction is printed, so the locks it holds often come from earlier statements in the same transaction.
Why do two INSERTs deadlock in MySQL?
Usually because each transaction first took a gap lock on the same range and then tried to insert into it. SELECT ... FOR UPDATE, UPDATE or DELETE on a key that does not exist locks the gap where it would be under REPEATABLE READ. Gap locks do not block each other, so both sessions get one, and then each INSERT waits for the other's gap lock with an insert intention lock. The analyzer reports this as gap lock vs. insert intention. Fix it with a single INSERT ... ON DUPLICATE KEY UPDATE on a unique key instead of check-then-insert, or with READ COMMITTED if you do not rely on gap locks. Concurrent inserts of the same unique values, and DELETE followed by INSERT of the same key, cause a related pattern with shared locks from the duplicate-key check.
What does Process X waits for ShareLock on transaction Y mean in PostgreSQL?
Process X wants a row that transaction Y has updated, deleted or locked, so it waits for transaction Y to end. It waits on the transaction rather than on the row, which is why the lock is reported as a ShareLock on a transaction ID. A deadlock is a cycle of such waits: process X waits for a transaction run by process Z, and Z waits for X. The client sees only these DETAIL lines; the server log also prints Process N: followed by each query. The usual fix is to lock or update rows in the same order in every transaction, for example SELECT ... ORDER BY id FOR UPDATE, and to retry on SQLSTATE 40P01. You can watch lock waits as they happen with pg_blocking_pids() in Chat2DB (download at chat2db.ai/download or use app.chat2db.ai).
