Chapter 17 · Performance Schema, sys Schema, Logs, and Observability

Performance Schema Instrumentation, Consumers, Statements, Waits, and Overhead

Instrument MariaDB deliberately with Performance Schema, distinguish instruments from consumers, read statement digests and waits, and control retention and overhead before drawing conclusions.

Advanced150–190 minutesInstrumentation + digest/wait labMariaDB Community 12.3.2 baselineCurriculum anchor: 11.8 LTS · verify target-version behaviorFree local MariaDB tooling · Last reviewed: August 2026

Learning outcomes

ServiceHub’s API latency has become erratic, but the team has only three kinds of evidence: application traces, a few global counters, and guesses about “disk waits.” MariaDB’s Performance Schema exists to expose server instrumentation in a structured way, but it is not a magic black box. You must know which code paths are instrumented, which consumers retain events, how long those events survive, and what measurement overhead you have accepted before interpreting the data.

01

Distinguish Performance Schema instruments from consumers and explain how an observed event reaches a table.

02

Verify whether Performance Schema is enabled and inspect the active statement, wait, and digest configuration before changing it.

03

Use statement digests to aggregate normalized SQL and use wait tables to investigate recent latency evidence.

04

Create a bounded before/after measurement window and explain how TRUNCATE/reset operations alter the meaning of counters.

05

Enable only targeted instrumentation, observe the effect, and avoid claiming root cause from one wait or cumulative counter.

Version and overhead discipline

This lesson uses MariaDB Community Server 12.3.2 as the current reference baseline and retains the curriculum anchor of 11.8 LTS. Performance Schema table availability, autosizing, default consumers, and instrumentation evolve. Verify the exact target server. Instrumentation consumes memory and CPU; enabling every history consumer on a busy production server without a reason is an observability failure, not observability maturity.

1. Mental model: instrument → event → consumer → table

An instrument is a named measurement point inside MariaDB—for example a statement class, mutex, file I/O path, socket operation, or memory allocation. A consumer decides whether events flow into current/history tables or summary structures. The Performance Schema itself is an in-memory instrumentation subsystem; its tables do not represent permanent audit history.

Layer Question to ask Example
Instrument Is this server activity being measured? statement/sql/select, file I/O, mutex/wait instruments
Consumer Where should measured events be retained or aggregated? events_statements_current, events_waits_history_long, statement digests
Event table What happened for a thread/event recently? events_waits_current, events_statements_history
Summary table What accumulated over a window? events_statements_summary_by_digest, wait summaries
Observation window Since when do these values mean anything? Server start, last TRUNCATE/reset, or a documented capture start

This chain matters because an empty table can mean “nothing happened,” but it can also mean the instrument or consumer was disabled, the history window rolled over, or you reset it. Always establish collection state before interpreting absence.

2. Verify collection state before changing it

sql · verify server baseline and Performance Schema
SELECT VERSION() AS server_version;SHOW VARIABLES LIKE 'performance_schema';SELECT COUNT(*) AS instrumentsFROM performance_schema.setup_instruments;SELECT NAME, ENABLEDFROM performance_schema.setup_consumersORDER BY NAME;

On a current server with Performance Schema enabled, setup_instruments should contain many named instruments and setup_consumers should show which destinations are enabled. Do not paste a “known good” matrix from another version: the actual table is the authority for this server.

sql · create a disposable ServiceHub observability workload
DROP DATABASE IF EXISTS servicehub17;CREATE DATABASE servicehub17 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;USE servicehub17;CREATE TABLE tickets (  ticket_id BIGINT PRIMARY KEY AUTO_INCREMENT,  customer_id BIGINT NOT NULL,  status ENUM('open','waiting','closed') NOT NULL,  priority TINYINT NOT NULL,  opened_at DATETIME(6) NOT NULL,  closed_at DATETIME(6) NULL,  INDEX ix_status_opened(status, opened_at),  INDEX ix_customer(customer_id)) ENGINE=InnoDB;CREATE TABLE ticket_events (  event_id BIGINT PRIMARY KEY AUTO_INCREMENT,  ticket_id BIGINT NOT NULL,  event_type VARCHAR(40) NOT NULL,  event_at DATETIME(6) NOT NULL,  payload VARCHAR(500) NULL,  INDEX ix_ticket_time(ticket_id, event_at)) ENGINE=InnoDB;INSERT INTO tickets(customer_id,status,priority,opened_at,closed_at)WITH RECURSIVE seq AS (  SELECT 1 AS n  UNION ALL SELECT n+1 FROM seq WHERE n < 500)SELECT 1000 + (n % 80),       CASE WHEN n % 7 = 0 THEN 'closed' WHEN n % 3 = 0 THEN 'waiting' ELSE 'open' END,       1 + (n % 5),       NOW(6) - INTERVAL n MINUTE,       CASE WHEN n % 7 = 0 THEN NOW(6) - INTERVAL (n-2) MINUTE ELSE NULL ENDFROM seq;INSERT INTO ticket_events(ticket_id,event_type,event_at,payload)SELECT ticket_id,       CASE WHEN ticket_id % 4=0 THEN 'comment' ELSE 'status_change' END,       opened_at + INTERVAL 30 SECOND,       RPAD('x', 120, 'x')FROM tickets;

The small ServiceHub fixture provides repeatable statements and I/O without pretending to be a production benchmark. The dataset is intentionally tiny; any latency numbers are local observations only.

3. Statement digests: group query shapes, not literal values

A digest normalizes literals so structurally similar statements can be aggregated. That lets you ask which query shapes dominate execution count or timed wait instead of grouping by every customer ID.

sql · generate two shapes and inspect digest summaries
USE servicehub17;SELECT COUNT(*) FROM tickets WHERE status='open';SELECT COUNT(*) FROM tickets WHERE status='waiting';SELECT * FROM tickets WHERE customer_id=1017 ORDER BY opened_at DESC LIMIT 10;SELECT SCHEMA_NAME,       DIGEST_TEXT,       COUNT_STAR,       ROUND(SUM_TIMER_WAIT/1000000000000,6) AS total_seconds,       ROUND(AVG_TIMER_WAIT/1000000000000,6) AS avg_seconds,       SUM_ROWS_EXAMINED,       SUM_ROWS_SENTFROM performance_schema.events_statements_summary_by_digestWHERE SCHEMA_NAME='servicehub17'ORDER BY SUM_TIMER_WAIT DESCLIMIT 10;

The status-count queries should collapse into one normalized digest even though the literal status differs. The table’s timing columns use picosecond-based timer units when timing is collected; converting to seconds makes human inspection easier. Digest summaries are cumulative within their current collection lifetime, so record the baseline/reset time before comparing two releases.

What this proves—and what it does not

A large SUM_TIMER_WAIT identifies a query shape that consumed substantial timed statement wait during the observation window. It does not prove the query is badly indexed, nor that MariaDB—not the application, network, or storage—is the root cause. Continue with plans, waits, row counts, and OS evidence.

4. Wait evidence and short history windows

sql · inspect enabled wait consumers and recent events
SELECT NAME, ENABLEDFROM performance_schema.setup_consumersWHERE NAME LIKE 'events_waits%';SELECT EVENT_NAME, OPERATION, OBJECT_SCHEMA, OBJECT_NAME,       TIMER_WAIT, NUMBER_OF_BYTESFROM performance_schema.events_waits_currentWHERE EVENT_NAME IS NOT NULLORDER BY TIMER_WAIT DESCLIMIT 20;

events_waits_current exposes the most recent monitored wait per thread. History tables retain only bounded recent windows; they are not durable logs. If the waits consumers you need are disabled, enable the smallest useful scope for the diagnostic window, then revert it.

sql · target a bounded wait-history capture
UPDATE performance_schema.setup_consumersSET ENABLED='YES'WHERE NAME IN ('events_waits_current','events_waits_history');-- Generate a small amount of known work in another session, then inspect:SELECT THREAD_ID, EVENT_NAME, OPERATION,       ROUND(TIMER_WAIT/1000000000000,6) AS wait_seconds,       OBJECT_SCHEMA, OBJECT_NAMEFROM performance_schema.events_waits_historyWHERE TIMER_WAIT IS NOT NULLORDER BY TIMER_WAIT DESCLIMIT 30;-- Restore your prior consumer state after the diagnostic window.

Do not assume the same consumer set is appropriate permanently. History depth is bounded and some sizing variables are startup-only; for longer retention, export/aggregate telemetry externally rather than inflating every in-server history table blindly.

5. Wrong approach: “enable everything and wait for the answer”

A common operator reaction is to turn every instrument and every history consumer on, leave them on indefinitely, and then sort the biggest counter. This increases measurement work and still lacks a hypothesis or time boundary.

sql · an intentionally over-broad change—do not use as a default
UPDATE performance_schema.setup_instrumentsSET ENABLED='YES', TIMED='YES';UPDATE performance_schema.setup_consumersSET ENABLED='YES';

The SQL may succeed, but “success” is the failure mode: you have expanded instrumentation globally without measuring its overhead or documenting why each class is required. Repair the process by restoring the previous state, selecting the minimum instruments/consumers for the question, and recording before/after throughput and latency under the same workload.

sql · capture a narrow baseline instead
-- Record the current state first:SELECT NAME, ENABLED FROM performance_schema.setup_consumers ORDER BY NAME;-- Example: leave statement digests enabled and activate only short statement historyUPDATE performance_schema.setup_consumersSET ENABLED='YES'WHERE NAME IN ('events_statements_current','events_statements_history','statements_digest');SELECT NOW(6) AS observation_window_started;

If you benchmark overhead, compare identical local workloads with warmup, concurrency, dataset, and cache state documented. A single laptop timing is not a universal Performance Schema overhead percentage.

6. Reset semantics: a counter without “since when?” is incomplete

Many Performance Schema summary tables can be reset with TRUNCATE TABLE. That is useful for a controlled experiment, but it destroys the previous in-memory summary. Treat a reset as an operational event worth recording.

sql · create a clean statement-digest observation window
TRUNCATE TABLE performance_schema.events_statements_summary_by_digest;SELECT NOW(6) AS digest_window_start;USE servicehub17;SELECT COUNT(*) FROM tickets WHERE status='open';SELECT COUNT(*) FROM tickets WHERE status='waiting';SELECT DIGEST_TEXT, COUNT_STAR,       ROUND(SUM_TIMER_WAIT/1000000000000,6) AS total_secondsFROM performance_schema.events_statements_summary_by_digestWHERE SCHEMA_NAME='servicehub17'ORDER BY SUM_TIMER_WAIT DESC;

After the reset, the result is easier to interpret because the window is controlled. It is also no longer evidence about the hour before the reset. Production telemetry pipelines should preserve context externally before destructive resets.

7. Production judgment, lab verification, and cleanup

Performance Schema is strongest when you ask a specific question—“which normalized statements accumulated most latency since deployment X?” or “which wait class rose during the incident?”—and combine that answer with plans, logs, and OS metrics. It is weakest when treated as a self-explaining ranking table.

Prerequisites and boundaries

Mandatory lab: free MariaDB Community Server 12.3.2 (curriculum anchor 11.8 LTS), Performance Schema enabled, and an account allowed to read Performance Schema plus create/drop the disposable ServiceHub database. Changing setup tables or global instrumentation should be done only in a disposable/admin lab. No Enterprise product, proxy, cluster, or external collector is required.

Check your understanding

  1. What is the difference between an instrument and a consumer?
  2. Why can an empty history table fail to prove that no waits occurred?
  3. What does a statement digest remove, and why is that useful?
  4. Why must you record the reset/start time when comparing digest summaries?
  5. Why is the largest wait counter a symptom rather than automatic root cause?
Review the answers

An instrument is a measurement point; a consumer controls where measured events are retained or summarized. Empty history may reflect disabled collection or bounded retention. Digests normalize literals so equivalent query shapes aggregate. Reset/start time defines the measurement window. A large wait identifies where time accumulated, but diagnosis still requires causal evidence from the query, storage, concurrency, topology, and OS layers.

sql · cleanup
DROP DATABASE IF EXISTS servicehub17;

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.