Chapter 16 · Performance Schema, sys Schema, Logs, Metrics, and Observability

sys Schema Views for Sessions, Statements, Waits, I/O, Memory, and Index Usage

Use the sys schema as a readable diagnostic layer over Performance Schema and metadata, but verify every expensive-statement, blocking-session, I/O, memory, or unused-index candidate against the real workload before acting.

Advanced150–210 minsys diagnosis + plan verification labMySQL Community Server 8.4.10 LTSsys / Performance SchemaLast reviewed: August 2026

Learning outcomes

Raw Performance Schema tables are precise, but operators repeatedly ask the same questions: Which sessions are blocking others? Which normalized statements consume the most time? Which files dominate I/O? How much instrumented memory is allocated? Which indexes have recorded no reads? The MySQL sys schema packages common joins, formatting, and summaries into DBA-friendly views. It is a convenience layer—not an independent source of truth.

01

Map sys schema views back to Performance Schema or metadata so you know what observation window and limitations remain.

02

Use sys.session/processlist, statement_analysis, innodb_lock_waits, I/O, memory, and index-usage views to generate diagnostic candidates.

03

Verify an expensive-statement candidate with EXPLAIN/EXPLAIN ANALYZE and optimizer/index evidence before tuning.

04

Reject the tempting “drop every index listed as unused” workflow and test workload coverage before changing schema.

05

Build a repeatable evidence ladder from friendly sys view to raw instrumentation, query plan, and host metric.

Declared lab baseline

Mandatory labs target MySQL Community Server 8.4.10 LTS on one disposable local instance. Performance Schema and the sys schema are expected in a normal initialized 8.4 instance, but every lesson first inspects availability/configuration instead of assuming a consumer, instrument, log sink, or privilege is enabled. Examples use a diagnostic administrator only where runtime instrumentation/log configuration requires it; application accounts remain least-privilege.

sys is a lens over instrumentation, not magic telemetry

A normal MySQL 8.4 initialization installs sys. Its views frequently read Performance Schema tables and format picoseconds/bytes into human-readable values. Many views have paired x$... variants that expose unformatted values useful for calculations. If Performance Schema collection is disabled or has not observed a representative workload, a sys view cannot invent missing evidence.

sql · prove sys exists and inspect representative views
SHOW DATABASES LIKE 'sys';SELECT * FROM sys.memory_global_total;SELECT conn_id, user, db, command, state, time, current_statementFROM sys.sessionORDER BY time DESCLIMIT 10;SELECT db, query, exec_count, total_latency, avg_latency,       rows_examined, rows_sent, full_scanFROM sys.statement_analysisWHERE db='servicehub_observe_lab'ORDER BY total_latency DESCLIMIT 10;

Find sessions and lock waits without killing by reflex

sys.session filters the process list toward foreground/user sessions, while sys.processlist can include broader thread information. The sys.innodb_lock_waits view summarizes InnoDB transactions waiting for row locks and identifies blocking connections/statements. A row in the view is a diagnosis lead—not automatic permission to kill the blocker. The blocker may be in the middle of a valid financial or maintenance transaction.

sql · Session A — hold one ServiceHub row deliberately
USE servicehub_observe_lab;START TRANSACTION;SELECT work_order_id, statusFROM work_ordersWHERE work_order_id=100FOR UPDATE;-- Keep this transaction open only for the lab.
sql · Session B — create a controlled wait
USE servicehub_observe_lab;SET SESSION innodb_lock_wait_timeout=8;UPDATE work_ordersSET labor_minutes=labor_minutes+5WHERE work_order_id=100;-- This waits behind Session A, then either proceeds after COMMIT/ROLLBACK-- or returns a lock-wait timeout.
sql · Observer session — inspect wait and blocker
SELECT wait_started, wait_age, locked_table,       waiting_pid, waiting_query,       blocking_pid, blocking_queryFROM sys.innodb_lock_waits\GSELECT conn_id,user,db,state,time,current_statementFROM sys.sessionWHERE conn_id IN (  SELECT waiting_pid FROM sys.innodb_lock_waits  UNION  SELECT blocking_pid FROM sys.innodb_lock_waits);

The safe repair is to understand transaction ownership and then end the lab transaction in Session A with ROLLBACK. In production, a kill decision requires application/transaction context, rollback cost, data-integrity considerations, and an incident owner.

Statement analysis generates a tuning candidate—not a tuning answer

Run the same intentionally awkward reporting shape several times so it appears in statement summaries. The expression YEAR(opened_at) makes the date component less directly useful for a range access path; adding a single-column status index looks tempting because the query filters by status, but the table already has (status, opened_at). A duplicate prefix does not repair the expression problem.

sql · repeat the awkward report shape
SELECT COUNT(*), SUM(labor_minutes)FROM servicehub_observe_lab.work_ordersWHERE status='OPEN'  AND YEAR(opened_at)=2026;SELECT db, query, exec_count, total_latency,       rows_examined, rows_sent, full_scanFROM sys.statement_analysisWHERE db='servicehub_observe_lab'ORDER BY total_latency DESCLIMIT 8;
sql · tempting but ineffective index
-- Do NOT accumulate indexes just because a query looks slow.-- CREATE INDEX ix_status_only ON servicehub_observe_lab.work_orders(status);SHOW INDEX FROM servicehub_observe_lab.work_orders;EXPLAIN ANALYZESELECT COUNT(*), SUM(labor_minutes)FROM servicehub_observe_lab.work_ordersWHERE status='OPEN'  AND YEAR(opened_at)=2026;

The corrected query expresses a half-open range so the existing composite index can provide both equality on status and a contiguous date range. Compare plan operators and actual iterator rows/timing on the same local dataset. Do not promise a fixed speedup.

sql · corrected predicate — same business meaning, indexable date range
EXPLAIN ANALYZESELECT COUNT(*), SUM(labor_minutes)FROM servicehub_observe_lab.work_ordersWHERE status='OPEN'  AND opened_at >= '2026-01-01'  AND opened_at <  '2027-01-01';

I/O and memory: readable summaries with scope limits

sql · I/O by file and instrumented memory
SELECT file, count_read, total_read,       count_write, total_written, totalFROM sys.io_global_by_file_by_bytesORDER BY total DESCLIMIT 12;SELECT * FROM sys.memory_global_total;SELECT event_name, current_allocFROM sys.memory_global_by_current_bytesORDER BY current_alloc DESCLIMIT 12;

These are server-instrumented observations. They do not replace OS metrics: filesystem cache, other processes, kernel memory, storage queueing, hypervisor throttling, and container limits can all matter outside MySQL's view.

The dangerous shortcut: “unused index” means “drop it”

sql · generate candidates only
SELECT object_schema, object_name, index_nameFROM sys.schema_unused_indexesWHERE object_schema='servicehub_observe_lab';SHOW INDEX FROM servicehub_observe_lab.work_orders;

schema_unused_indexes means no index-usage events were observed in the Performance Schema window. A recently restarted server, a quiet weekend, monthly job, failover path, foreign-key need, or infrequent critical lookup can all make a necessary index look unused. In this lab, ix_demo_unused_region may appear simply because we deliberately did not run region queries.

Candidate → representative window → reversible test → drop

Observe long enough to include important workload periods. Check constraints and index prefixes. Capture baseline plans. If appropriate, use an invisible-index experiment first (Chapter 8/9), then monitor plans/errors/write cost before permanently dropping anything.

Production judgment

Use sys for fast orientation and communication: human-readable latency, file I/O, memory, process, lock, and index views are excellent incident starting points. When a decision has consequence—killing a session, dropping an index, changing memory, rewriting SQL—drop down to underlying Performance Schema counters, query plans, transaction state, and host metrics. Friendly formatting should reduce cognitive load, not lower the evidence standard.

Next we add MySQL's durable/semidurable log streams. Logs answer different questions from in-memory instrumentation, and their overhead/retention/privacy characteristics are different.

Knowledge check

  1. What does sys.statement_analysis add compared with raw digest tables?
  2. Why should a row in sys.innodb_lock_waits not automatically trigger KILL?
  3. Why is a single-column status index ineffective for the YEAR(opened_at) example?
  4. What does schema_unused_indexes actually prove?
  5. Why must sys I/O/memory views be correlated with OS metrics?
Reveal answers
  1. It presents normalized statement summary data in a more readable form, but it still depends on underlying Performance Schema collection and observation windows.
  2. The blocker may own a valid transaction; killing it can cause expensive rollback or business failure. Determine ownership, duration, criticality, and repair plan first.
  3. The table already has a composite index beginning with status, while the expression on opened_at prevents a straightforward date range. Rewrite the predicate and compare plans.
  4. Only that the Performance Schema observed no usage events for that index during the retained observation period; it does not prove the index is unnecessary.
  5. MySQL cannot see every host-level cause such as filesystem cache, competing processes, queue latency, swap, container limits, or hypervisor throttling.

Authoritative references

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only assets compatible with the Ethereum/ERC-20 network. Do not send TRC-20/TRON assets.