MySQL Performance Schema Monitoring

Complete guide to MySQL Performance Schema for monitoring query performance, identifying bottlenecks, and optimizing database operations.

MySQL Performance Schema Monitoring

The Performance Schema is MySQL's built-in instrumentation framework that provides detailed insights into server execution at runtime. It captures performance data without significantly impacting server performance, making it the go-to tool for monitoring and troubleshooting MySQL deployments.


🎯 What is Performance Schema?

Performance Schema collects data about server events, providing visibility into:

CategoryWhat It Tracks
StatementsQuery execution times, errors, rows examined
WaitsLock waits, I/O waits, synchronization
StagesQuery execution stages and progress
TransactionsTransaction state and history
MemoryMemory allocation and usage
ConnectionsUser connections and thread states

💡 Key Benefit: Performance Schema provides real-time performance data with minimal overhead (typically 5-10%), unlike the slow query log which only captures queries exceeding a threshold.


🔧 Enabling and Configuring Performance Schema

Check Current Status

-- Check if Performance Schema is enabled
SHOW VARIABLES LIKE 'performance_schema';

-- Check Performance Schema sizing
SHOW VARIABLES LIKE 'performance_schema_%';

-- View enabled instruments
SELECT NAME, ENABLED, TIMED
FROM performance_schema.setup_instruments
WHERE NAME LIKE '%statement%'
LIMIT 20;

-- View enabled consumers
SELECT * FROM performance_schema.setup_consumers;

Configuration

# /etc/mysql/mysql.conf.d/performance_schema.cnf

[mysqld]
# Enable Performance Schema (default: ON in MySQL 5.7+)
performance_schema = ON

# Memory allocation settings
performance_schema_events_statements_history_size = 100
performance_schema_events_statements_history_long_size = 10000
performance_schema_events_waits_history_size = 20
performance_schema_events_waits_history_long_size = 15000

# Digest sizing
performance_schema_digests_size = 25000
performance_schema_max_digest_length = 4096

# Statement truncation
performance_schema_max_sql_text_length = 4096

Enable/Disable Specific Instruments

-- Enable all statement instruments
UPDATE performance_schema.setup_instruments
SET ENABLED = 'YES', TIMED = 'YES'
WHERE NAME LIKE 'statement/%';

-- Enable wait instruments
UPDATE performance_schema.setup_instruments
SET ENABLED = 'YES', TIMED = 'YES'
WHERE NAME LIKE 'wait/%';

-- Enable history consumers
UPDATE performance_schema.setup_consumers
SET ENABLED = 'YES'
WHERE NAME LIKE '%history%';

-- Persist settings (MySQL 8.0+)
SET PERSIST performance_schema = ON;

📊 Essential Monitoring Queries

Top Queries by Execution Time

-- Find slowest queries by total time
SELECT
    DIGEST_TEXT AS query,
    COUNT_STAR AS exec_count,
    ROUND(SUM_TIMER_WAIT/1000000000000, 4) AS total_time_sec,
    ROUND(AVG_TIMER_WAIT/1000000000, 2) AS avg_time_ms,
    ROUND(MAX_TIMER_WAIT/1000000000, 2) AS max_time_ms,
    SUM_ROWS_EXAMINED AS rows_examined,
    SUM_ROWS_SENT AS rows_sent,
    ROUND(SUM_ROWS_EXAMINED / NULLIF(COUNT_STAR, 0), 0) AS avg_rows_examined
FROM performance_schema.events_statements_summary_by_digest
WHERE SCHEMA_NAME IS NOT NULL
    AND DIGEST_TEXT NOT LIKE 'SHOW%'
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 20;

Queries with Full Table Scans

-- Queries doing full table scans
SELECT
    DIGEST_TEXT AS query,
    COUNT_STAR AS exec_count,
    SUM_NO_INDEX_USED AS no_index_count,
    SUM_NO_GOOD_INDEX_USED AS no_good_index_count,
    ROUND(SUM_TIMER_WAIT/1000000000000, 4) AS total_time_sec,
    SUM_ROWS_EXAMINED AS rows_examined
FROM performance_schema.events_statements_summary_by_digest
WHERE (SUM_NO_INDEX_USED > 0 OR SUM_NO_GOOD_INDEX_USED > 0)
    AND SCHEMA_NAME IS NOT NULL
ORDER BY SUM_NO_INDEX_USED DESC
LIMIT 20;

Queries with Temporary Tables

-- Queries creating temporary tables (potential optimization targets)
SELECT
    DIGEST_TEXT AS query,
    COUNT_STAR AS exec_count,
    SUM_CREATED_TMP_TABLES AS tmp_tables,
    SUM_CREATED_TMP_DISK_TABLES AS tmp_disk_tables,
    ROUND(SUM_CREATED_TMP_DISK_TABLES / NULLIF(SUM_CREATED_TMP_TABLES, 0) * 100, 2) AS disk_tmp_pct,
    ROUND(SUM_TIMER_WAIT/1000000000000, 4) AS total_time_sec
FROM performance_schema.events_statements_summary_by_digest
WHERE SUM_CREATED_TMP_TABLES > 0
ORDER BY SUM_CREATED_TMP_DISK_TABLES DESC
LIMIT 20;

Queries with Errors

-- Queries generating errors
SELECT
    DIGEST_TEXT AS query,
    COUNT_STAR AS exec_count,
    SUM_ERRORS AS error_count,
    SUM_WARNINGS AS warning_count,
    ROUND(SUM_ERRORS / NULLIF(COUNT_STAR, 0) * 100, 2) AS error_pct
FROM performance_schema.events_statements_summary_by_digest
WHERE SUM_ERRORS > 0
ORDER BY SUM_ERRORS DESC
LIMIT 20;

🔍 Wait Analysis

Top Wait Events

-- What is MySQL waiting on?
SELECT
    EVENT_NAME,
    COUNT_STAR AS wait_count,
    ROUND(SUM_TIMER_WAIT/1000000000000, 4) AS total_wait_sec,
    ROUND(AVG_TIMER_WAIT/1000000000, 2) AS avg_wait_ms,
    ROUND(MAX_TIMER_WAIT/1000000000, 2) AS max_wait_ms
FROM performance_schema.events_waits_summary_global_by_event_name
WHERE COUNT_STAR > 0
    AND EVENT_NAME != 'idle'
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 20;

I/O Waits by File

-- Which files have the most I/O wait?
SELECT
    FILE_NAME,
    EVENT_NAME,
    COUNT_STAR AS io_count,
    ROUND(SUM_TIMER_WAIT/1000000000000, 4) AS total_wait_sec,
    ROUND(SUM_NUMBER_OF_BYTES_READ / 1024 / 1024, 2) AS read_mb,
    ROUND(SUM_NUMBER_OF_BYTES_WRITE / 1024 / 1024, 2) AS write_mb
FROM performance_schema.file_summary_by_instance
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 20;

Lock Waits

-- Current lock waits
SELECT
    r.trx_id AS waiting_trx_id,
    r.trx_mysql_thread_id AS waiting_thread,
    r.trx_query AS waiting_query,
    b.trx_id AS blocking_trx_id,
    b.trx_mysql_thread_id AS blocking_thread,
    b.trx_query AS blocking_query
FROM performance_schema.data_lock_waits w
JOIN information_schema.innodb_trx r ON r.trx_id = w.REQUESTING_TRANSACTION_ID
JOIN information_schema.innodb_trx b ON b.trx_id = w.BLOCKING_TRANSACTION_ID;

-- Lock wait summary
SELECT
    OBJECT_SCHEMA,
    OBJECT_NAME,
    COUNT_STAR AS lock_count,
    ROUND(SUM_TIMER_WAIT/1000000000000, 4) AS total_wait_sec
FROM performance_schema.table_lock_waits_summary_by_table
WHERE COUNT_STAR > 0
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 20;

📈 Connection and Thread Analysis

Connection Statistics

-- Connections by user
SELECT
    USER,
    HOST,
    CURRENT_CONNECTIONS,
    TOTAL_CONNECTIONS,
    ROUND(MAX_SESSION_CONNECT_ATTRS_SIZE / 1024, 2) AS max_attrs_kb
FROM performance_schema.accounts
WHERE TOTAL_CONNECTIONS > 0
ORDER BY CURRENT_CONNECTIONS DESC;

-- Connection errors
SELECT *
FROM performance_schema.host_cache
WHERE COUNT_ERRORS > 0;

Thread States

-- What are threads doing right now?
SELECT
    THREAD_ID,
    NAME AS thread_name,
    TYPE,
    PROCESSLIST_ID,
    PROCESSLIST_USER AS user,
    PROCESSLIST_HOST AS host,
    PROCESSLIST_DB AS db,
    PROCESSLIST_COMMAND AS command,
    PROCESSLIST_STATE AS state,
    PROCESSLIST_TIME AS time_sec,
    LEFT(PROCESSLIST_INFO, 100) AS query_preview
FROM performance_schema.threads
WHERE TYPE = 'FOREGROUND'
    AND PROCESSLIST_ID IS NOT NULL
ORDER BY PROCESSLIST_TIME DESC;

Active Queries

-- Currently executing queries
SELECT
    ps.ID AS process_id,
    ps.USER,
    ps.HOST,
    ps.DB,
    ps.COMMAND,
    ps.TIME AS running_seconds,
    ps.STATE,
    LEFT(ps.INFO, 200) AS query,
    t.PROCESSLIST_STATE AS detailed_state
FROM information_schema.PROCESSLIST ps
LEFT JOIN performance_schema.threads t
    ON ps.ID = t.PROCESSLIST_ID
WHERE ps.COMMAND != 'Sleep'
    AND ps.INFO IS NOT NULL
ORDER BY ps.TIME DESC;

🔧 Memory Analysis

Memory Usage by Code Area

-- Memory allocation by component
SELECT
    SUBSTRING_INDEX(EVENT_NAME, '/', 2) AS code_area,
    ROUND(SUM(CURRENT_NUMBER_OF_BYTES_USED) / 1024 / 1024, 2) AS current_mb,
    ROUND(SUM(HIGH_NUMBER_OF_BYTES_USED) / 1024 / 1024, 2) AS high_water_mb
FROM performance_schema.memory_summary_global_by_event_name
GROUP BY SUBSTRING_INDEX(EVENT_NAME, '/', 2)
HAVING current_mb > 1
ORDER BY current_mb DESC;

Memory by Connection

-- Memory usage per connection
SELECT
    t.PROCESSLIST_USER AS user,
    t.PROCESSLIST_HOST AS host,
    ROUND(SUM(m.CURRENT_NUMBER_OF_BYTES_USED) / 1024 / 1024, 2) AS current_mb
FROM performance_schema.memory_summary_by_thread_by_event_name m
JOIN performance_schema.threads t ON m.THREAD_ID = t.THREAD_ID
WHERE t.TYPE = 'FOREGROUND'
GROUP BY t.PROCESSLIST_USER, t.PROCESSLIST_HOST
ORDER BY current_mb DESC;

📋 Creating Monitoring Views

Comprehensive Query Statistics View

CREATE VIEW v_query_stats AS
SELECT
    SUBSTRING(DIGEST_TEXT, 1, 100) AS query_sample,
    COUNT_STAR AS executions,
    ROUND(SUM_TIMER_WAIT/1000000000000, 4) AS total_time_sec,
    ROUND(AVG_TIMER_WAIT/1000000000, 2) AS avg_ms,
    ROUND(MAX_TIMER_WAIT/1000000000, 2) AS max_ms,
    SUM_ROWS_EXAMINED AS rows_examined,
    SUM_ROWS_SENT AS rows_returned,
    ROUND(SUM_ROWS_EXAMINED / NULLIF(SUM_ROWS_SENT, 0), 0) AS exam_to_sent_ratio,
    SUM_CREATED_TMP_DISK_TABLES AS disk_tmp_tables,
    SUM_NO_INDEX_USED AS full_scans,
    SCHEMA_NAME AS schema_name
FROM performance_schema.events_statements_summary_by_digest
WHERE SCHEMA_NAME IS NOT NULL
    AND COUNT_STAR > 0;

System Health View

CREATE VIEW v_system_health AS
SELECT
    'Queries Executed' AS metric,
    SUM(COUNT_STAR) AS value
FROM performance_schema.events_statements_summary_global_by_event_name
UNION ALL
SELECT
    'Avg Query Time (ms)',
    ROUND(AVG(AVG_TIMER_WAIT)/1000000000, 2)
FROM performance_schema.events_statements_summary_global_by_event_name
WHERE COUNT_STAR > 0
UNION ALL
SELECT
    'Full Table Scans',
    SUM(SUM_NO_INDEX_USED)
FROM performance_schema.events_statements_summary_by_digest
UNION ALL
SELECT
    'Temp Tables on Disk',
    SUM(SUM_CREATED_TMP_DISK_TABLES)
FROM performance_schema.events_statements_summary_by_digest
UNION ALL
SELECT
    'Current Connections',
    COUNT(*)
FROM performance_schema.threads
WHERE TYPE = 'FOREGROUND' AND PROCESSLIST_ID IS NOT NULL;

🚀 Automated Monitoring Script

-- Procedure to capture performance snapshot
DELIMITER //

CREATE PROCEDURE sp_capture_performance_snapshot()
BEGIN
    -- Create snapshot table if not exists
    CREATE TABLE IF NOT EXISTS performance_snapshots (
        id BIGINT AUTO_INCREMENT PRIMARY KEY,
        snapshot_time DATETIME DEFAULT CURRENT_TIMESTAMP,
        digest VARCHAR(64),
        query_sample TEXT,
        exec_count BIGINT,
        total_time_sec DECIMAL(15,4),
        avg_time_ms DECIMAL(15,2),
        rows_examined BIGINT,
        rows_sent BIGINT,
        tmp_disk_tables BIGINT,
        full_scans BIGINT,
        INDEX idx_time (snapshot_time),
        INDEX idx_digest (digest)
    );

    -- Insert current snapshot
    INSERT INTO performance_snapshots (
        digest, query_sample, exec_count, total_time_sec,
        avg_time_ms, rows_examined, rows_sent, tmp_disk_tables, full_scans
    )
    SELECT
        DIGEST,
        LEFT(DIGEST_TEXT, 500),
        COUNT_STAR,
        ROUND(SUM_TIMER_WAIT/1000000000000, 4),
        ROUND(AVG_TIMER_WAIT/1000000000, 2),
        SUM_ROWS_EXAMINED,
        SUM_ROWS_SENT,
        SUM_CREATED_TMP_DISK_TABLES,
        SUM_NO_INDEX_USED
    FROM performance_schema.events_statements_summary_by_digest
    WHERE COUNT_STAR > 10
        AND SCHEMA_NAME IS NOT NULL;

    -- Cleanup old snapshots (keep 7 days)
    DELETE FROM performance_snapshots
    WHERE snapshot_time < DATE_SUB(NOW(), INTERVAL 7 DAY);
END //

DELIMITER ;

-- Schedule the snapshot
CREATE EVENT evt_performance_snapshot
    ON SCHEDULE EVERY 1 HOUR
    ON COMPLETION PRESERVE
DO
    CALL sp_capture_performance_snapshot();

⚠️ Troubleshooting with Performance Schema

Find Long-Running Queries

-- Queries running longer than 60 seconds
SELECT
    ps.ID,
    ps.USER,
    ps.HOST,
    ps.DB,
    ps.TIME AS seconds_running,
    ps.STATE,
    ps.INFO AS query
FROM information_schema.PROCESSLIST ps
WHERE ps.COMMAND != 'Sleep'
    AND ps.TIME > 60
ORDER BY ps.TIME DESC;

Identify Connection Issues

-- Connection errors by host
SELECT
    IP,
    HOST,
    COUNT_HANDSHAKE_ERRORS,
    COUNT_AUTHENTICATION_ERRORS,
    COUNT_MAX_USER_CONNECTIONS_ERRORS,
    FIRST_ERROR_SEEN,
    LAST_ERROR_SEEN
FROM performance_schema.host_cache
WHERE COUNT_ERRORS > 0;

Reset Statistics

-- Reset statement statistics
TRUNCATE TABLE performance_schema.events_statements_summary_by_digest;

-- Reset wait statistics
TRUNCATE TABLE performance_schema.events_waits_summary_global_by_event_name;

-- Reset all statistics
CALL sys.ps_truncate_all_tables(FALSE);

✅ Best Practices Checklist

Configuration

  • [ ] Enable Performance Schema in production
  • [ ] Configure appropriate history sizes
  • [ ] Enable relevant instruments for your needs
  • [ ] Monitor Performance Schema memory usage

Monitoring

  • [ ] Track top queries regularly
  • [ ] Alert on high wait times
  • [ ] Monitor for full table scans
  • [ ] Review temporary table usage

Operations

  • [ ] Reset statistics after major changes
  • [ ] Capture snapshots for trending
  • [ ] Correlate with application metrics
  • [ ] Document baseline performance

🎓 Conclusion

Performance Schema is an indispensable tool for MySQL monitoring and optimization. By understanding its capabilities and implementing proper monitoring queries, you can quickly identify and resolve performance bottlenecks in your database.

💡 Pro Tip: Combine Performance Schema data with the sys schema views for easier analysis. The sys schema provides user-friendly formatting of Performance Schema data.


About the Author

Jamaurice Holt is a Cloud Database Expert and AWS Solutions Architect with over 10 years of experience in database optimization, high availability, and cloud migrations. Specializing in MySQL, SQL optimization, and enterprise database solutions.

Related Articles

Need help with MySQL performance monitoring? Contact us for expert database consulting.

Continue Reading

Leadership

Leadership in Motion: Technical Leadership Lessons from Database Operations

How a decade of database administration shaped my approach to technical leadership — lessons on incident response, mentoring engineers,...

July 10, 2024 · Read article →
Performance

Database Performance Tuning: Lessons from the Trenches

Real-world strategies for optimizing database performance in high-traffic production environments — query plan analysis, indexing, caching,...

August 22, 2024 · Read article →
AI

AI Governance and Hardware: Why Sovereign Refusal Belongs in Silicon

Software guardrails can be prompted around. The real frontier in AI safety is anchoring refusal below the model — at the hardware level. A...

September 15, 2024 · Read article →