# Mysql Slow Query Diagnose

> Slow query diagnosis with root cause analysis and index optimization recommendations. Identifies TOP N slow queries, analyzes scan efficiency, calculates index hit rate, and suggests covering indexes — turning a 20-minute manual diagnosis into 8 seconds.

- Skill: `openocta/mysql-slow-query-diagnose` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add openocta/mysql-slow-query-diagnose`
- Raw SKILL.md: https://api.skillmd.com/api/skills/openocta/mysql-slow-query-diagnose/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: openocta (https://skillmd.com/u/openocta)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/openocta/mysql-slow-query-diagnose

---


# MySQL Slow Query Diagnose (Diagnosis + Optimization)

## Overview

Slow queries are the #1 cause of database performance degradation. This skill goes beyond simply listing slow queries — it performs **root cause analysis** for each one: how many rows scanned vs returned, which index was used (or missed), and what index to add to fix it.

Key capabilities:
- Identify TOP N slow queries by execution time, lock time, or scan rows
- Calculate **index hit rate** (rows_examined / rows_sent) — the key metric for query efficiency
- Analyze EXPLAIN plans to identify full table scans, filesort, temporary tables
- Suggest **covering indexes** with estimated improvement
- Output a prioritized action list sorted by impact

**Real-world result**: A DBA team reduced average slow query resolution from 20 minutes to 8.3 seconds — **144x improvement** — by having AI perform the diagnosis automatically.

## When to Use

Apply this skill when:
- Database response time suddenly increases
- Application reports query timeouts
- Monitoring shows slow query count spike
- Periodic performance review / health check
- After schema changes to verify query performance
- Developer asks "why is this query slow?"

## Required MCP Server

| Tool | Purpose | Example |
|------|---------|---------|
| `db_query` | Run diagnostics and EXPLAIN | `db_query("EXPLAIN SELECT ...")` |
| `db_get_slow_log` | Retrieve slow query entries | `db_get_slow_log(hours=1, limit=20)` |
| `db_describe_table` | Check table indexes | `db_describe_table("orders")` |
| `db_get_status` | Get performance counters | `db_get_status("Slow_queries")` |

### Required MySQL Privileges

- `SELECT` on `mysql.slow_log` or `performance_schema.events_statements_summary_by_digest`
- `PROCESS` — for EXPLAIN on other sessions' queries

## Diagnostic Workflow

### Step 1: Collect Slow Queries

```sql
-- Option A: MySQL 5.7+ performance_schema (recommended)
SELECT
  DIGEST_TEXT AS sql_template,
  COUNT_STAR AS exec_count,
  ROUND(SUM_TIMER_WAIT / 1000000000000, 3) AS total_time_sec,
  ROUND(AVG_TIMER_WAIT / 1000000000000, 3) AS avg_time_sec,
  ROUND(MAX_TIMER_WAIT / 1000000000000, 3) AS max_time_sec,
  ROUND(SUM_ROWS_EXAMINED / COUNT_STAR, 0) AS avg_rows_examined,
  ROUND(SUM_ROWS_SENT / COUNT_STAR, 0) AS avg_rows_sent,
  SUM_NO_INDEX_USED AS no_index_count
FROM performance_schema.events_statements_summary_by_digest
WHERE DIGEST_TEXT IS NOT NULL
  AND SUM_TIMER_WAIT > 0
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 20;

-- Option B: slow_log table (if enabled)
SELECT
  start_time,
  user_host,
  query_time,
  lock_time,
  rows_sent,
  rows_examined,
  LEFT(sql_text, 300) AS sql_fragment
FROM mysql.slow_log
WHERE start_time > DATE_SUB(NOW(), INTERVAL 1 HOUR)
ORDER BY query_time DESC
LIMIT 20;
```

Also check overall slow query status:

```sql
SHOW GLOBAL STATUS LIKE 'Slow_queries';
SHOW GLOBAL STATUS LIKE 'Slow_launch_threads';
SHOW VARIABLES LIKE 'long_query_time';
SHOW VARIABLES LIKE 'slow_query_log';
```

### Step 2: Analyze Each Top Query

For each slow query identified, run:

```sql
-- Get the EXPLAIN plan
EXPLAIN FORMAT=JSON {the_query};

-- Check current table indexes
SHOW INDEX FROM {table_name};

-- Check table size and row count
SELECT
  TABLE_NAME,
  TABLE_ROWS,
  ROUND(DATA_LENGTH / 1024 / 1024, 2) AS data_mb,
  ROUND(INDEX_LENGTH / 1024 / 1024, 2) AS index_mb
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = '{db}' AND TABLE_NAME = '{table}';
```

### Step 3: Calculate Efficiency Metrics

For each query, compute:

| Metric | Formula | Interpretation |
|--------|---------|---------------|
| **Index Hit Rate** | `rows_sent / rows_examined × 100%` | <1% = severe index miss |
| **Scan Efficiency** | `rows_examined / table_rows` | >50% = nearly full scan |
| **Lock Ratio** | `lock_time / query_time × 100%` | >30% = lock contention |
| **Exec Frequency** | `COUNT_STAR` from digest | High + slow = critical |

Classification:

```
IF index_hit_rate < 1% AND no_index_used:
  → Type: MISSING_INDEX (Critical)
  → Action: Add index
ELSE IF scan_efficiency > 50%:
  → Type: FULL_SCAN (High)
  → Action: Optimize query or add covering index
ELSE IF lock_ratio > 30%:
  → Type: LOCK_CONTENTION (Medium)
  → Action: Check concurrent transactions
ELSE IF avg_time > threshold:
  → Type: DATA_VOLUME (Medium)
  → Action: Archive or partition
```

### Step 4: Generate Index Recommendations

For each missing index case, suggest:

```sql
-- Example: query is SELECT * FROM orders WHERE status = ? AND create_time > ?
-- Current: full table scan, 12M rows examined, 380 rows returned
-- Index hit rate: 380/12800000 = 0.003% → CRITICAL

-- Recommended index:
ALTER TABLE orders ADD INDEX idx_status_createtime (status, create_time);

-- Expected improvement:
-- rows_examined: 12,000,000 → ~40,000 (300x reduction)
-- query_time: 23.7s → <0.1s
```

Index design rules:
1. **Equality first** — columns with `=` condition go first in composite index
2. **Range last** — columns with `>`, `<`, `BETWEEN` go last
3. **Covering index** — if possible, include all SELECT columns to avoid table lookup
4. **Order matters** — `(status, create_time)` ≠ `(create_time, status)`

## Output Templates

### Initial Report

```
🔍 MySQL Slow Query Diagnostic Report
══════════════════════════════════════
📋 Instance: {host}:{port}
   Analysis Period: Last {hours} hours
   Slow Query Threshold: {threshold}s
   Total Slow Queries: {count}

🔴 TOP {N} Slow Queries (by total impact)
─────────────────────────────────────────

  TOP 1 ⚡ Total Impact: {total_time}s across {count} executions
  ┌─────────────────────────────────────────────┐
  │ SQL: {sql_template}                         │
  │ Avg Time: {avg}s  |  Max: {max}s            │
  │ Rows Examined: {examined}  |  Returned: {sent}│
  │ Index Hit Rate: {rate}% 🔴 MISSING INDEX     │
  │                                             │
  │ Root Cause: Full table scan on {table}       │
  │   → No index on ({columns})                 │
  │                                             │
  │ ✅ Recommended:                              │
  │   ALTER TABLE {table} ADD INDEX              │
  │     idx_{name} ({columns});                  │
  │   Expected: {current}s → <0.1s              │
  └─────────────────────────────────────────────┘

  TOP 2 ⚡ ...
  TOP 3 ⚡ ...

📊 Summary
  | Rank | SQL Pattern | Executions | Avg Time | Hit Rate | Fix |
  |------|------------|------------|----------|----------|-----|
  | 1    | {pattern}  | {count}    | {time}s  | {rate}%  | Add index |
  | 2    | ...        | ...        | ...      | ...      | ... |

⚡ Priority Actions
  1. [Critical] Add index on {table}({cols}) — fixes TOP 1, est. 300x speedup
  2. [High] Add index on {table}({cols}) — fixes TOP 2
  3. [Medium] Schedule batch query for off-peak — TOP 3 is legitimate report
```

### After Index Applied (Follow-up)

```
✅ Index Verification
══════════════════
  Query: {sql_template}
  Before: avg {old}s, examined {old_rows} rows
  After:  avg {new}s, examined {new_rows} rows
  Improvement: {factor}x faster 🎉
```

## Example Usage

**User**: "Check slow queries on db-prod-01, last 1 hour"

**AI Response**:
```
I'll analyze slow queries from the last hour.

🔍 MySQL Slow Query Diagnostic Report
══════════════════════════════════════
📋 Instance: db-prod-01:3306
   Analysis Period: Last 1 hour
   Total Slow Queries: 62

🔴 TOP 3 Slow Queries

  TOP 1 ⚡ 23.7s avg × 47 executions = 1,114s total impact
  ┌────────────────────────────────────────────────┐
  │ SELECT * FROM order_detail                     │
  │   WHERE create_time > ? AND status = ?         │
  │                                                │
  │ Avg Time: 23.7s | Max: 45.2s                  │
  │ Rows Examined: 12,800,000 | Returned: 380      │
  │ Index Hit Rate: 0.003% 🔴                       │
  │                                                │
  │ Root Cause: Full table scan — no index on      │
  │   (status, create_time)                        │
  │                                                │
  │ ✅ Fix: ALTER TABLE order_detail ADD INDEX      │
  │      idx_status_createtime (status, create_time)│
  │   Expected: 23.7s → <0.1s (237x faster)        │
  └────────────────────────────────────────────────┘

  TOP 2 ⚡ 8.4s avg × 12 executions = 100.8s total impact
  │ Full scan on user_order (5.6M rows)
  │ Fix: ADD INDEX (merchant_id)

  TOP 3 ⚡ 4.2s avg × 3 executions = 12.6s total impact
  │ Index hit, batch report — schedule off-peak

⚡ Priority Actions:
  1. [Critical] Add (status, create_time) on order_detail
  2. [High] Add (merchant_id) on user_order
  3. [Info] Schedule TOP 3 report query to 02:00-05:00
```

## Best Practices

### Index Design Checklist

- [ ] Equality columns first, range columns last
- [ ] High-cardinality columns first in composite index
- [ ] Consider covering index for hot queries (avoid table lookup)
- [ ] Check existing indexes before adding (avoid duplicates)
- [ ] Estimate index size: `rows × avg_col_size × 1.5`
- [ ] Add indexes online (MySQL 5.6+ supports `ALTER TABLE ... ALGORITHM=INPLACE`)

### When NOT to Add an Index

- Query runs infrequently (batch job once a day)
- Table is small (< 10,000 rows) — full scan may be faster
- Write-heavy table — each index adds write overhead
- Query already uses a good index but returns many rows (data volume issue)

### Common Anti-Patterns

| Anti-Pattern | Why It's Bad | Fix |
|-------------|-------------|-----|
| `SELECT *` with index | Can't use covering index | Select only needed columns |
| `LIKE '%keyword%'` | Leading wildcard can't use index | Use full-text search or `LIKE 'keyword%'` |
| `OR` conditions | May cause index merge | Rewrite as `UNION ALL` or use composite index |
| Function on indexed column `WHERE YEAR(dt)=2024` | Index not used | Use range: `WHERE dt >= '2024-01-01'` |

## Quick Reference

| Action | Query |
|--------|-------|
| Top slow queries (perf_schema) | `SELECT ... FROM events_statements_summary_by_digest ORDER BY SUM_TIMER_WAIT DESC` |
| Top slow queries (slow_log) | `SELECT ... FROM mysql.slow_log WHERE start_time > ... ORDER BY query_time DESC` |
| EXPLAIN a query | `EXPLAIN FORMAT=JSON SELECT ...` |
| Show table indexes | `SHOW INDEX FROM table_name` |
| Table size | `SELECT ... FROM information_schema.TABLES WHERE TABLE_NAME='...'` |
| Check slow query log status | `SHOW VARIABLES LIKE 'slow_query_log'` |
| Enable slow query log | `SET GLOBAL slow_query_log = ON` |

**Use this skill for intelligent slow query diagnosis — not just listing queries, but explaining WHY they're slow and HOW to fix them.**

