Automatic Lock Forensics & Row‑Lock Tuning for Aurora/RDS MySQL

Automatically capture row lock/blocking forensics on Aurora/RDS MySQL every few seconds—complete with the exact blocking SQL—then use a proven checklist to reduce contention.

Table of Contents

TL;DR: When row lock waits hit Aurora/RDS MySQL, you often miss the culprit by the time you log in. This post shows how to automatically capture lock/blocking forensics every few seconds—including the exact blocking SQL—so you can diagnose incidents after the fact. We’ll also walk through a practical tuning checklist to reduce contention long‑term.


Who this is for

  • Teams running Aurora MySQL 3.x (MySQL 8.0)—Serverless v2 or provisioned—and classic RDS MySQL 8.0.
  • You want drop‑in tooling that is safe for production, low‑overhead, and easy to turn on/off.

Using MySQL 5.7 / Aurora 2.x? See the Variants section for a compatible procedure.


What you’ll build

  • A dba.lock_wait_captures table that records snapshots of waiters, blockers, SQL text on both sides, lock/table/index details, and wait ages.
  • A scheduled event (default every 5 seconds) that writes a snapshot only when lock waits exist.
  • A daily purge job (default 7 days) to keep storage in check.
  • Handy queries to find worst offenders and hot tables/indexes.

Prereqs

  • Permissions to create objects in a dba schema and change Performance Schema settings.
  • sys schema available (default on RDS/Aurora). If not, use the fallback that queries performance_schema directly.
  • Event Scheduler enabled on the instance/cluster.

Step 1 — Enable lightweight instrumentation

What: Turn on just enough Performance Schema (P_S) consumers and lock instruments to capture the SQL text and the lock-wait relationships.

Why: By default, MySQL may not retain statement history or time lock waits. Enabling these lets us correlate who waited on whom with the exact SQL.

How: These are dynamic toggles (no restart). If your environment resets P_S on restart/patching, re-run this snippet at startup.
These consumers/instruments are safe for production in typical OLTP workloads and are dynamic (no restart).

Tip: Re‑run the snippet after maintenance windows if your environment resets P_S settings.


Step 2 — Create the capture table

What: A durable dba.lock_wait_captures table to store snapshots of waiters, blockers, lock metadata, and SQL text.

Why: Incidents are transient; persisting the state every few seconds lets you investigate after the fact.

How: One InnoDB table with a composite PK (captured_at, waiting_thread_id). Retention is handled by a scheduled purge in Step 4.


Step 3 — Procedure to snapshot current lock waits (MySQL 8.0+/Aurora 3.x)

What: A stored procedure dba.capture_locks() that inserts a single snapshot row-set for each sampling tick.

Why: Centralizes the logic and allows the scheduler to call a single routine. We only write when waits exist to keep overhead and table size low.

How: Query sys.innodb_lock_waits (a curated view over P_S + InnoDB internals) and insert the results into dba.lock_wait_captures with the current timestamp.
Preferred approach: use sys.innodb_lock_waits to keep the joins simple.

Fallback (same engines) if sys isn’t available

What/Why: Some builds omit the sys schema. This variant joins performance_schema.data_lock_waits and friends directly to produce equivalent output.

How: The join pulls waiting/blocking transactions, lock metadata, and the most recent SQL text from events_statements_current.
Use performance_schema.data_lock_waits with companion tables.

Using MySQL 5.7/Aurora 2.x? Replace Step 3 with the 5.7 variant that queries information_schema.innodb_* tables.


Step 4 — Schedule automatic snapshots & a daily purge

What: Two MySQL Events—one to take snapshots every few seconds and one to purge old rows daily.

Why: Automation ensures coverage during off-hours. Purge keeps storage bounded.

How: Enable event_scheduler and create events in the dba schema. On RDS/Aurora, persist event_scheduler=ON in a DB parameter group.


Step 5 — Read the forensics after an incident

What: A few investigation queries you’ll use 90% of the time.

How to read them:

  • Last hour shows the most recent waits so you can align with app/error logs.
  • Worst waits surfaces the biggest pain points by wait age.
  • Hot tables/indexes identifies data structures with contention.
  • Frequent blockers highlights app users/hosts and a sample of the blocking SQL text.

Last hour:

Worst waits:

Hot tables/indexes (24h):

Frequent blockers:


Row‑lock tuning playbook (high impact first)

  1. Keep transactions tiny: don’t hold a txn across sleeps, API calls, or long loops; autocommit single‑row ops.
  2. Index for precision: every FK column indexed; make sure UPDATE/DELETE WHERE clauses are backed by targeted indexes; prefer PK/range‑narrow scans.
  3. Reduce gap locks: consider transaction_isolation = READ-COMMITTED (session/global) for OLTP; test before rollout.
  4. Lock smarter: avoid range FOR UPDATE; use point key locks and 8.0’s FOR UPDATE SKIP LOCKED / NOWAIT for worker queues.
  5. Consistent lock order across code paths (A→B everywhere) to kill deadlocks.
  6. Batch writes: commit in small chunks (e.g., 500 rows); shard hot counters/queues.
  7. Auto‑increment: ensure innodb_autoinc_lock_mode = 2 (default in 8.0).
  8. DDL hygiene: schedule off‑hours; set lock_wait_timeout for DDL sessions.
  9. Observe: innodb_print_all_deadlocks=ON to error log; use Performance Insights “Waits” for time‑slice context.

Aurora/RDS specifics

  • Serverless v2: Raising min ACUs won’t fix logical conflicts but can reduce CPU latency, shortening lock hold times. Offload heavy reads to reader endpoints.
  • Parameter changes (isolation level, timeouts) belong in a DB parameter group; most are dynamic in 8.0.
  • Logs: Deadlock traces stream to the MySQL error log (CloudWatch Logs on RDS/Aurora).

Variants

MySQL 5.7 / Aurora MySQL 2.x

Use the 5.7 procedure that reads from information_schema.innodb_lock_waits, innodb_trx, and innodb_locks, with PROCESSLIST joins for SQL text. (We include the full 5.7 version in the GitHub Gist linked below.)


Troubleshooting & safety

  • No rows captured? Ensure event_scheduler=ON, events are ENABLED, and lock waits actually occurred.
  • SQL text is NULL? Make sure events_statements_history[_long] and thread_instrumentation consumers are ON.
  • Overhead concerns? Increase interval to 10–15s, or capture only during known windows.
  • Persistence: Put event_scheduler=ON in a DB parameter group; re-run instrumentation after restarts if needed.

Copy‑paste checklist

Goal: Get from zero to useful captures in minutes.

1) One-time setup

  • Create schema & table

  • Enable instrumentation (safe in prod)

  • Create the procedure (pick one variant)

2) Turn on automation

  • Enable the scheduler and create events

3) Sanity checks

  • Verify the view exists (8.0 engines)

  • Or verify P_S objects

  • Generate a quick test wait (optional)

4) Nice-to-haves

  • Send MySQL error log to CloudWatch; set innodb_print_all_deadlocks=ON
  • Add a Grafana/PI panel for row lock waits to spot spikes
  • Bake Step 1 instrumentation into an init job so it survives restarts

Have a project or a problem?

Talk with a senior engineer for practical recommendations—no obligation.

Leave a comment

Leave a Reply

Your email address will not be published. Required fields are marked *

Related Posts

Categories

Get a free consultation from Reliable Penguin

Submit the form—or for immediate service call 866-649-7984.