Updated October 2026: MySQL Monitor has had a major update. The single table of counters is now a live dashboard with per-second rates, alerts, health checks and process and top-query views. See What’s New below.
As a sysadmin or DBA, you need a fast way to see how MySQL is doing. I’ve used plenty of monitoring tools over the years, but often I just want something light that I can run in a terminal and still get real, actionable insight from. That’s why I wrote this script.
The script has changed a lot since I first published it here, but the goal hasn’t: open a terminal, see what matters, and move on. It’s still one bash script that needs nothing beyond awk
and the MySQL client tools. It isn’t meant to replace the deeper tools like these.
mysql-monitor-script: the dashboard on a MySQL 8.0 server handling around 1k queries per second. (screenshot)
What’s New in This Version
If you used the original script, here’s what has changed. The install command is the same, so updating takes one line.
- Live rates as well as totals. The original version printed one table of counters, mostly totals since the server started. The new version is a live dashboard: per-second rates measured between refreshes, next to the since-boot averages.
- Panels for throughput, connections, the InnoDB buffer pool, temp tables and sorts, tables and locks, and the system (CPU, memory, swap, load), with sparklines of recent history and usage bars against limits like
max_connections
andtable_open_cache
. - Marks and alerts. A ✓, ⚠ or ✗ on every line, and an alerts box with plain-English hints that name the setting to look at.
- New views: health checks (
h
) with suggested settings, processes (p
) with sort, filter, kill and a history of recent queries, and top queries (t
) fromperformance_schema
statement digests. - New checks the old version didn’t have: refused connections (
max_connections
reached), InnoDB row lock waits, redo log size vs write rate, the query cache, slow query log status, and running threads vs CPU cores. - Fits any terminal: up to three columns, a compact 80×24 view, a details view with descriptions, and scrolling.
- Runs almost anywhere.
--once
prints a single snapshot for ssh and cron. It works with MySQL 5.7, 8.0 and MariaDB, and with gawk, mawk or busybox awk. - The classic table is still there. Press
r
for it. It now has a per-second column.
What MySQL Monitor Does
Every few seconds, the script reads MySQL’s status counters and settings with mysqladmin, works out what changed since the last refresh, and adds the system’s CPU, memory, swap and load. Each value is shown as a rate for the last interval, with a sparkline of the last minute and the average since the server started, so you can tell a spike from a long-term trend.
Only values that need attention are shown in bold colour, and the alerts box sums up anything that needs action. On a healthy server the screen stays calm, so a problem stands out immediately. The layout follows your terminal: two or three columns on a wide screen, one on a narrow one, and a description of each counter when there’s room. The health, process, top queries and raw counters views are covered below.
It’s still minimal by design. It shows only the metrics database administrators care about during routine checks.
Get MySQL Monitor
You can download MySQL Monitor from GitHub, or download and run it in one step:
curl -O https://raw.githubusercontent.com/haydenjames/mysqlmonitor-script/main/mysqlmonitor.sh && chmod +x mysqlmonitor.sh && ./mysqlmonitor.sh
Run it as root, or put your credentials in ~/.my.cnf
under a [client]
section with user=
and password=
, then chmod 600
the file. The script never accepts a password on the command line. To refresh every second, run ./mysqlmonitor.sh 1
. For all the options, run ./mysqlmonitor.sh --help
.
Keys
?
help,h
health checks,p
processes,t
top queries,r
the classic counters table,d
details,q
quit.space
pauses and resumes,+
and-
change the refresh interval, the arrow keys and PgUp/PgDn scroll or move the selection, andEsc
returns to the dashboard.- In the process view,
o
changes the sort order,s
shows sleeping connections,f
filters,k
kills the selected query (after asking you to confirm), andc
clears the recent-queries history.
The help screen (?). Only values that need attention are shown in bold colour.
The Views
Health Checks (h)
This view builds the tuning advice from this blog into the tool itself. It runs more than twenty checks, grouped into InnoDB, connections, tables, queries and system. Each one shows its current value, its value since the server started, and the target. When a check fails, the line below names the setting to change, its current value and a suggested value. For example: table_open_cache = 4000, Open_tables = 4000. Try 8000.
It also covers things the old table couldn’t: clients refused because max_connections
was reached, InnoDB row lock waits and how long they last, how many hours of writes the redo log can hold, whether the query cache is enabled on MySQL 5.7 and MariaDB (it shouldn’t be; MySQL 8.0 removed it), and whether the slow query log is on.
Health checks (h) on a healthy MySQL 8.0 server. Since-boot figures need about a day of normal traffic to be meaningful, and the live ratios appear once an interval has enough activity to judge.
Processes (p)
This is the live process list, longest-running first, with the full text of each query. You can sort it by time, ID, user or database. Select a query with the arrow keys and press k
to kill it; the script asks you to confirm first.
Below the live list, queries caught in earlier refreshes stay on screen, grouped by pattern, with how often they ran and for how long. That lets you spot repeats even on a server where most connections last only a few milliseconds.
Processes (p): a query that has been running for almost two minutes, with the recent-queries history below.
Top Queries (t)
A process list is only a snapshot: it shows whatever happens to be running at the moment you look. On a busy server where each query takes a millisecond, it catches almost nothing, even at thousands of queries per second.
MySQL keeps exact per-statement totals in performance_schema
, and this view reads them on every refresh. For each statement pattern it shows calls per second, average and maximum run time, its share of total query time, and rows examined per row returned, which is the clearest sign of a missing index. It also flags statements that create temp tables on disk, spill sorts to disk or use no index at all. On the server in the screenshot, 2,400 queries per second come down to about sixty patterns, and the first few rows are where the indexing work is.
performance_schema
is on by default in MySQL 5.7 and 8.0. MariaDB ships with it off, so add performance_schema = ON
to my.cnf
and restart. The view needs the mysql
client and SELECT access to performance_schema
. If anything is missing, it tells you what.
Top queries (t) on a Magento database: exact totals for each statement, refreshed every five seconds.
Raw Counters (r)
The original table is still there if you prefer plain numbers. It now has a per-second column next to each total.
Raw counters (r): the classic view.
Understanding the Output
The dashboard shows the numbers at a glance, but knowing what each variable means makes them far more useful. Below is a short explanation of each one and how to read it.
A quick guide to the labels: “total” and “avg” count since the server started, and anything ending in “/s” was measured between the last two refreshes. Hit ratios show the current interval once it has enough activity to judge, and the since-boot figure until then.
Innodb_buffer_pool_size
The total size of the InnoDB buffer pool, which caches data and indexes in memory. A well-sized buffer pool keeps disk I/O to a minimum. If it’s too small for your workload, you’ll see far more disk reads and writes, and performance suffers.
Innodb_buffer_pool_pages_free
The number of free pages in the buffer pool. On a server that has been running for a while, the pool is normally almost full, and that’s fine as long as the hit ratio stays high. Zero free pages together with waits for free pages (below) means you need to increase innodb_buffer_pool_size
.
Innodb_buffer_pool_wait_free
How many times MySQL had to wait for a free page in the buffer pool. Any non-zero value is a red flag: writes are being delayed because the buffer pool is too small.
Innodb_buffer_pool_write_requests
The number of write requests to the buffer pool. A high value is normal on write-heavy servers. If it comes with high disk I/O, look at optimizing your queries or increasing the buffer pool size.
Innodb_log_waits
How many times InnoDB had to wait for the log buffer to be flushed. Even a small non-zero value means innodb_log_buffer_size
is too small for your workload.
Innodb_os_log_written
Bytes written to the redo log. The health view divides your redo log size by this write rate to show how many hours of writes the log can hold. Less than an hour means InnoDB is checkpointing constantly. If so, raise innodb_log_file_size
, or innodb_redo_log_capacity
on MySQL 8.0.30 and later.
Innodb_row_lock_waits
How often a transaction had to wait for a row locked by another transaction. A steadily climbing value means contention: keep transactions short, and index the columns used in the WHERE clauses of your UPDATE and DELETE statements. The health view also shows the average wait time.
Threads_connected
The number of open connections, shown against max_connections
. If it gets close to the limit, you need connection pooling or faster queries to handle the load. The monitor leaves its own connection out of this count.
Threads_running
The number of threads running a query right now, shown against your CPU core count. If it stays above the number of cores, queries are queuing for CPU. That usually points to long-running queries or lock contention.
Connection_errors_max_connections
Clients refused because max_connections
was reached. Any increase here means users have already seen errors. Raise the limit, or fix connection leaks and pooling in your application.
Aborted_clients and Aborted_connects
Connections that were dropped after they connected, and connection attempts that failed. A few are normal. A steady rate means failed logins, timeouts (wait_timeout
), packets that are too large (max_allowed_packet
) or network problems.
Slow_queries
The number of queries that took longer than long_query_time
. The dashboard shows it both as a rate and as a share of all queries, because one slow query per second is nothing at 2,000 queries per second but serious at five. Use the slow query log to find them. The health view tells you if the log is off, and warns if long_query_time
is still at the default of 10 seconds.
Key_reads
The number of index blocks read from disk instead of the key buffer. Lower is better: frequent disk reads mean the key buffer is too small to cache your indexes. These are MyISAM counters, so on an InnoDB-only server they stay at zero.
Key_writes
The number of index blocks written to disk. Zero, or a value that grows slowly compared with your queries per second, is what you want. The per-second column shows how often it happens.
Open_tables
The number of tables the server has open, shown as a bar against table_open_cache
. If it sits at the limit while tables keep being reopened, queries will start to slow down. Increase table_open_cache
if needed.
Opened_tables
The total number of tables opened since the server started. A steadily rising per-second value means frequent table cache misses, which you can usually fix by tuning table_open_cache
.
Questions
Statements sent to the server by clients. Unlike Queries
, it doesn’t count statements run inside stored programs, or protocol commands such as COM_PING
and COM_STMT_PREPARE
. The dashboard’s queries per second is this counter measured between refreshes.
Select_scan
The number of full table scans. A high value usually means missing indexes, and proper indexing brings it down. The top queries view shows which statements are responsible.
Select_full_join
Joins that couldn’t use an index on the joined table, so MySQL scanned that table for every row. Even a small rate is worth fixing: add indexes on the joined columns.
Table_locks_waited
The number of times a table lock had to wait. Any non-zero value means contention. Convert the tables to InnoDB, or reduce locking in your queries.
Sort_merge_passes
The number of merge passes MySQL needed while sorting. A high value means sort_buffer_size
is too small and sorts are spilling to disk. Increase it, but do it with caution: the buffer is allocated for every sort.
Innodb_data_reads
The number of reads from InnoDB data files on disk. A high value can mean the buffer pool is too small, or simply that your workload is read-heavy.
Innodb_data_writes
The number of writes to InnoDB data files. A high value is normal for write-heavy workloads, but unexpected spikes are worth investigating.
Created_tmp_disk_tables
Internal temporary tables created on disk instead of in memory. The dashboard shows this as a percentage of all temp tables. On MySQL 5.7 and MariaDB, internal temp tables use the MEMORY engine: the in-memory limit is the smaller of tmp_table_size
and max_heap_table_size
, and BLOB or TEXT columns force them to disk. On MySQL 8.0, the default TempTable engine is limited by temptable_max_ram
overall and, from 8.0.28, by tmp_table_size
per table. It has handled BLOB and TEXT columns in memory since 8.0.13.
Uptime
How long the server has been running. Since-boot statistics need at least 24 hours of uptime before you can rely on them. The per-second values are accurate from the second refresh onwards.
InnoDB Buffer Pool Free
Free space in the InnoDB buffer pool, as a size and as a percentage of the pool. On a server that has been running a while this is normally small, and what matters more is the hit ratio below. If reads are going to disk and you have RAM to spare, increase innodb_buffer_pool_size
.
Queries per Second (QPS)
The load on the database. High QPS is normal on a busy server, but sudden spikes can point to inefficient application code or unoptimized queries. The sparkline next to it shows the last minute.
Temp Tables on Disk
The percentage of temporary tables created on disk instead of in memory. Disk-based temp tables are much slower, so keep this as close to 0% as you can. If it’s high, raise tmp_table_size
and max_heap_table_size
on MySQL 5.7 and MariaDB, or tmp_table_size
and temptable_max_ram
on MySQL 8.0.
Thread Cache Hit Ratio
The percentage of connections that reused a cached thread instead of creating a new one. Close to 100% means connections are handled efficiently. If it’s low, increase thread_cache_size
.
Table Cache Hit Ratio
The percentage of table accesses served from the table cache without reopening the table. A high ratio cuts overhead and speeds up queries. If it’s low, increase table_open_cache
, especially if your workload touches many tables.
InnoDB Buffer Pool Hit Ratio
The percentage of reads served from the buffer pool instead of disk. You want this close to 100%. If it drops below 99%, increase innodb_buffer_pool_size
to cut disk I/O, especially on a server with high QPS.
CPU and Load
Whole-system CPU usage, plus the 1, 5 and 15 minute load averages. The top queries view shows how much of that is MySQL by converting total query time into CPU cores: “0.18 cores” means the database is using about a fifth of one core.
Memory
System memory in use, plus how much is available. “Available” is the number to watch, because it includes cache and buffers the system can reclaim. If it drops below 10%, MySQL can slow down noticeably, and the script will warn you. When memory is tight, check innodb_buffer_pool_size
and all the *_buffer_size
settings. Also read: MySQL Database Performance: Avoid this common mistake and how to measure memory usage correctly.
Swap
How much swap is in use. MySQL performs badly when any of it gets swapped out, so this should stay close to zero. Lower vm.swappiness
, and leave memory headroom for mysqld rather than giving every last byte to the buffer pool.
Between these variables and the health checks, you get a clear picture of how your MySQL server is doing. Once you know how to read them, troubleshooting and tuning get a lot easier. If you haven’t tried the script yet, Download it from GitHub and let me know how it goes!
Also, read more articles tagged “MySQL”…
Get in Touch
If you have suggestions, improvements or better ways to write this script, leave a comment below or open an issue on GitHub. I’m sure I’ve missed something or done something the hard way, and that’s part of why I’m sharing it. It started as a basic script to save myself time. It’s nothing groundbreaking, but I hope you find it as useful as I do.
Happy monitoring!
Published: January 24th 2025 | Last updated: October 24th 2026
MySQL Monitor just got its biggest update since I first shared it back in January 2025.
It’s now a live dashboard instead of a single table, with per-second rates and sparklines, ✓//✗ marks and an alerts box with tuning hints.
New views cover health checks (
h
), running processes with kill (p
) and top queries fromperformance_schema
(t
). That last one finally catches the fast queries a process list never shows.It fits any terminal size and runs on MySQL 5.7/8.0 and MariaDB. The classic table is still on the
r
key.Same one-line install as before. I’ve updated the post above with the details and new screenshots. Let me know how it runs on your servers.