MySQL Integration
Monitor MySQL query performance, connection pressure, InnoDB internals, schema growth, index usage, lock contention and replication health from a single Watchlog agent.
Works with MySQL and MariaDB. The agent detects which one it is talking to and adapts — the two diverge on replication statements and lock tables, and version detection handles that rather than assuming.
Supported Platforms
Supported Versions
| Version | Status |
|---|---|
| MySQL 8.0+ | Fully supported — performance_schema.data_locks gives the richest lock detail |
| MySQL 5.7 | Supported — locks come from information_schema.innodb_lock_waits instead |
| MariaDB 10.x | Supported — never adopted data_locks or the 8.0.22 replica terminology, both handled |
Version differences are detected, not assumed. Two boundaries matter:
- MySQL 8.0 introduced
performance_schema.data_locks/data_lock_waits. Below that (and on all MariaDB) the agent readsinformation_schema.innodb_lock_waits. - MySQL 8.0.22 renamed the replication statements (
SHOW REPLICA STATUS). MariaDB kept the old terminology permanently.
Required Privileges
CREATE USER 'watchlog_monitor'@'%' IDENTIFIED BY 'your_password';
GRANT PROCESS, REPLICATION CLIENT ON *.* TO 'watchlog_monitor'@'%';
GRANT SELECT ON performance_schema.* TO 'watchlog_monitor'@'%';
GRANT SELECT ON information_schema.* TO 'watchlog_monitor'@'%';
FLUSH PRIVILEGES;
No SUPER, no ALL PRIVILEGES, and no access to your application data — the agent never reads a row from a user table.
What each grant buys:
| Grant | Enables | Without it |
|---|---|---|
PROCESS | SHOW GLOBAL STATUS, SHOW ENGINE INNODB STATUS, thread counts | The integration cannot report |
SELECT on performance_schema | Query digests, index statistics, lock waits | Those three tabs are hidden |
SELECT on information_schema | Schema and table sizes | No storage detail |
REPLICATION CLIENT | SHOW REPLICA STATUS | No replication tab |
A missing grant disables that section only. The integration stays connected.
Capability Detection
Several features depend on server configuration rather than privileges, and are probed rather than assumed:
| Capability | Requires |
|---|---|
performanceSchema | performance_schema = ON — off by default on some distributions |
queryDigest | The statement digest consumer enabled and SELECT on performance_schema |
indexStats | table_io_waits_summary_by_index_usage readable |
locks | MySQL 8 data_lock_waits, or 5.7/MariaDB innodb_lock_waits |
binlog | log_bin = ON |
slowQueryLog | slow_query_log = ON |
When performance_schema is off, the Queries, Indexes and Locks tabs are hidden and a capability note explains why — rather than showing three empty tabs.
SHOW VARIABLES LIKE 'performance_schema';
Turning it on requires a restart and costs a few percent of memory. It is what makes query-level analysis possible.
What Watchlog Collects
Server Version and flavour, uptime, connections against max_connections, running and cached threads, aborted clients and connects, questions and slow-query counters, open tables against table_open_cache, and temporary tables created on disk.
Queries (needs performance_schema) Per normalized digest: executions, total and average latency, rows examined against rows sent, temporary tables, sorts, and full-scan counts — ranked by impact (executions × average latency), which is the database time actually consumed. That ordering surfaces a 2 ms query running 50 000 times ahead of one 4-second report; sorting by average hides it.
InnoDB Buffer pool size, usage and hit rate, dirty page percentage, log waits, row lock waits and average wait time, pending IO, and deadlock counts.
Schemas and tables Per schema: table count, data and index size. Per table: rows, data length, index length, average row length and engine. System schemas (mysql, sys, information_schema, performance_schema) are excluded everywhere — they would otherwise dominate every "largest table" list.
Indexes (needs performance_schema) Per index: reads, writes and usage, with unused candidates flagged. An unused index is reads = 0 — not reads = 0 && writes = 0, which would exclude every index on a write-heavy table. UNIQUE and PRIMARY indexes are never proposed for removal; they enforce correctness regardless of scan count.
Locks Blocking and waiting transactions with the statements involved, plus deadlock counts.
Replication Role, IO and SQL thread state, seconds behind source, relay log position, and the last error.
Health Score and Insights
A 0–100 score computed server-side, with a written reason on every deduction:
| Signal | Judged on |
|---|---|
| Connection pressure | Threads connected against max_connections |
| Running threads | Concurrency actually executing, not merely connected |
| Query latency | Worst per-interval average across tracked digests |
| Buffer pool | Hit rate, judged only once enough reads happened to be meaningful |
| Dirty pages | Share of the buffer pool awaiting flush |
| InnoDB log waits | Writes that stalled waiting for log space |
| Table cache | Table open rate against table_open_cache |
| Disk temp tables | Temporary tables that spilled to disk |
| Lock waits, deadlocks | Active waits and deadlock events |
| Replication | Stopped threads, last error, and seconds behind |
A number is not a verdict. A low buffer pool hit rate over 200 reads means nothing; the score requires volume before it judges.
Slow Queries
Watchlog does not read the MySQL slow query log file. It uses performance_schema.events_statements_summary_by_digest, which gives the same information without the file, without a threshold to configure, and without the write cost.
slowQuery.thresholdMs (default 100) only decides what the dashboard calls slow. The agent, the API and the UI all use that one number so they cannot disagree.
Query text is normalized by MySQL itself before Watchlog ever sees it — the digest text has literals replaced with ?. Parameter values never leave your host.
Tips
performance_schema timings are picoseconds. Watchlog converts them; if you compare raw values with what the dashboard shows, divide by 1e9 for milliseconds.
Collection Intervals
| Section | Interval |
|---|---|
| Status, connections, InnoDB, queries, locks, replication | every collection cycle (60s) |
| Schema and table sizes | 300s |
| Index statistics | 300s |
Safety
Watchlog is read-only. It never runs any of the following:
ANALYZE TABLE · OPTIMIZE TABLE · ALTER TABLE · KILL · FLUSH TABLES · SET GLOBAL · DROP of anything · any write to any table
There are no full table scans for monitoring, and every monitoring statement carries a timeout so it can never be the thing that hangs a busy server.
Advanced Options
{
"service": "mysql",
"monitor": true,
"host": "localhost",
"port": "3306",
"username": "watchlog_monitor",
"password": "your_password",
"database": [],
"ssl": false,
"advanced": {
"enabled": true,
"queries": true,
"schema": true,
"indexes": true,
"locks": true,
"replication": true,
"maxDigests": 200,
"maxTables": 300,
"maxIndexes": 500,
"maxLockWaits": 50,
"schemaIntervalSeconds": 300,
"indexIntervalSeconds": 300
},
"slowQuery": {
"thresholdMs": 100
}
}
| Field | Default | Purpose |
|---|---|---|
database | [] | Only the legacy collector needs this. The advanced collector discovers schemas itself. |
ssl | false | TLS to the server |
advanced.maxDigests | 200 | Query shapes tracked per scrape, ranked by impact |
advanced.maxTables | 300 | Tables per scrape, largest first |
advanced.maxLockWaits | 50 | Lock-wait pairs captured |
slowQuery.thresholdMs | 100 | What counts as slow, consistently across agent, API and UI |
Multiple Instances
Add one entry per instance. Each is identified by its host:port:
[
{
"service": "mysql",
"monitor": true,
"host": "127.0.0.1",
"port": "3306",
"username": "watchlog_monitor",
"password": "your_password"
},
{
"service": "mysql",
"monitor": true,
"host": "127.0.0.1",
"port": "3307",
"username": "watchlog_monitor",
"password": "your_password"
}
]
Troubleshooting
| Symptom | Cause |
|---|---|
| Queries, Indexes and Locks tabs missing | performance_schema is off, or the user lacks SELECT on it |
| Queries tab empty but the others work | The statement digest consumer is disabled |
| Replication tab missing | No REPLICATION CLIENT grant, or the server is not a replica |
Access denied on connect | The user's host pattern does not match where the agent connects from — 'watchlog_monitor'@'%' vs @'localhost' |
| Table sizes look stale | They refresh every 300s by design |
| Deadlock count always zero | MySQL exposes cumulative deadlocks only via SHOW ENGINE INNODB STATUS; MariaDB has an Innodb_deadlocks status variable. Both are handled, but the value differs in meaning. |
