When a query hangs and nothing obvious is wrong, it is usually a lock. These are the queries I keep going back to, in the order I tend to run them.

All of these are for Aurora MySQL 3, which is MySQL 8.x. The lock tables moved to performance_schema in 8.0, so the older information_schema.innodb_lock_waits versions of these queries will not work.

1. Current lock waits

Who is blocking whom. This is the first thing to run.

SELECT
    r.trx_id             AS waiting_trx_id,
    r.trx_mysql_thread_id AS waiting_thread,
    r.trx_query          AS waiting_query,
    r.trx_wait_started    AS wait_started,
    TIMESTAMPDIFF(SECOND, r.trx_wait_started, NOW()) AS wait_age_sec,
    b.trx_id             AS blocking_trx_id,
    b.trx_mysql_thread_id AS blocking_thread,
    b.trx_query          AS blocking_query,
    b.trx_started        AS blocking_trx_started
FROM performance_schema.data_lock_waits w
JOIN information_schema.innodb_trx b ON b.trx_id = w.blocking_engine_transaction_id
JOIN information_schema.innodb_trx r ON r.trx_id = w.requesting_engine_transaction_id
ORDER BY wait_age_sec DESC;

2. All current locks held

What is locked, by whom, and with which lock type and mode.

SELECT
    l.ENGINE_TRANSACTION_ID AS trx_id,
    l.OBJECT_SCHEMA,
    l.OBJECT_NAME,
    l.LOCK_TYPE,
    l.LOCK_MODE,
    l.LOCK_STATUS,
    l.LOCK_DATA,
    t.trx_mysql_thread_id AS thread_id,
    t.trx_query
FROM performance_schema.data_locks l
JOIN information_schema.innodb_trx t ON t.trx_id = l.ENGINE_TRANSACTION_ID
ORDER BY l.OBJECT_NAME;

3. Long-running transactions

The common root cause of held locks. A transaction left open keeps its locks until it commits or rolls back.

SELECT
    trx_id,
    trx_state,
    trx_started,
    TIMESTAMPDIFF(SECOND, trx_started, NOW()) AS duration_sec,
    trx_mysql_thread_id AS thread_id,
    trx_query,
    trx_rows_locked,
    trx_rows_modified
FROM information_schema.innodb_trx
ORDER BY trx_started ASC;

4. Full process list with state

Useful for spotting anything sitting in a Waiting for... state.

SELECT
    ID,
    USER,
    HOST,
    DB,
    COMMAND,
    TIME AS time_sec,
    STATE,
    LEFT(INFO, 200) AS query
FROM information_schema.processlist
WHERE COMMAND != 'Sleep'
ORDER BY TIME DESC;

5. Metadata lock waits

DDL against DML contention, which is what you want when an ALTER TABLE is stuck behind something else.

SELECT
    p.PROCESSLIST_ID,
    p.PROCESSLIST_USER,
    p.PROCESSLIST_HOST,
    m.OBJECT_TYPE,
    m.OBJECT_SCHEMA,
    m.OBJECT_NAME,
    m.LOCK_TYPE,
    m.LOCK_STATUS
FROM performance_schema.metadata_locks m
JOIN performance_schema.threads p ON p.THREAD_ID = m.OWNER_THREAD_ID
WHERE m.LOCK_STATUS = 'PENDING'
   OR m.OBJECT_SCHEMA NOT IN ('performance_schema', 'mysql');

6. InnoDB engine status

A verbose text blob, but it carries the deadlock history and a lock summary that the tables above do not give you.

SHOW ENGINE INNODB STATUS\G