MySQL / MariaDB Performance
Slow MySQL or MariaDB database: diagnose with the slow query log and MySQLTuner
Before touching my.cnf, you need to know what is slow and why. Here's the method we follow: measure, read MySQLTuner critically, tune the few settings that matter, then work on queries, which is almost always where the real gain lies.
The principle: measure before tuning
"10 settings to speed up MySQL" articles share one flaw: they hand out values without knowing your workload. A slow database is rarely slow because of a single setting. Most of the time, a handful of queries account for most of the time spent, because an index is missing or a query written for 1,000 rows now runs on 10 million.
The approach comes down to five steps, in this order: find the expensive queries, read the server's global indicators, adjust the structural settings, fix queries and indexes, and check that the problem really is the database.
Step 1: enable the slow query log
The slow query log records queries that exceed long_query_time. It's the source of truth: it shows what the database actually runs, not what you assume. It can be enabled live, without a restart; connections already open keep the old threshold until they reconnect.
Enable the slow query log
-- Live, no restart (then persist it in my.cnf)
-- SET GLOBAL only applies to new connections
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1; -- seconds, decimals allowed
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';
-- MariaDB: execution plan in the log (file output: log_output includes FILE)
SET GLOBAL log_slow_verbosity = 'query_plan,explain';Two precautions. log_queries_not_using_indexes looks useful, but on a busy database it floods the log with trivial queries (fully reading a 20-row table is not a problem): enable it occasionally, not permanently. And a threshold set too low on a heavily loaded server generates a lot of disk writes: start at 1 second, then go lower.
On MariaDB, log_slow_verbosity adds the execution plan (query_plan, explain): full table scan, on-disk temporary table, on-disk sort. You see straight away why the query is slow. These details are only written when log_output includes FILE; engine statistics are a separate option, engine (MariaDB 10.6.15 and 10.11.5 or later).
A raw multi-GB log is unreadable. pt-query-digest (Percona Toolkit) groups identical queries regardless of parameters and ranks them by total time consumed. The ranking matters: a 50 ms query run 2 million times a day often weighs more than a 30-second query run once a night.
Aggregate the log with pt-query-digest
pt-query-digest /var/log/mysql/slow.log > digest.txtStep 2: read MySQLTuner critically
MySQLTuner is an open source Perl script that reads server variables and counters, then produces a diagnosis and recommendations. It supports MySQL, MariaDB, Percona Server and Galera. It changes nothing: it reads and comments.
Download and run MySQLTuner
wget https://mysqltuner.pl/ -O mysqltuner.pl
# without --pass, the script prompts for the password
perl mysqltuner.pl --host 127.0.0.1 --user dbaEssential prerequisite: at least 24 hours of uptime since the last restart. Its ratios rely on counters accumulated since startup (or since the last FLUSH STATUS). Right after a restart, caches are cold and the numbers mean nothing. The script's authors say so themselves.
Its most useful sections: theoretical maximum memory usage (an approximation: roughly global buffers plus per-connection buffers multiplied by max_connections), the share of reads served by the InnoDB buffer pool, the proportion of temporary tables created on disk, aborted connections, and security warnings (password-less accounts, remote root access).
What you can follow
- Security warnings: anonymous accounts, empty passwords, root reachable remotely.
- The memory warning: if theoretical peak usage exceeds RAM, the server risks being killed by the OS under load.
- A buffer pool clearly smaller than the data actually being read.
- The fragmentation and tables without primary keys it flags: they deserve a look.
What you should challenge
- "Increase
query_cache_size": the query cache no longer exists in MySQL 8.0 and has been disabled by default on MariaDB since 10.1.7. On a write-heavy or highly concurrent database, it creates contention. It only pays off on some read-mostly workloads, and that has to be measured. - "Increase
join_buffer_size/sort_buffer_size": these buffers are allocated per connection, sometimes several times per query. Inflating them hides a missing index and drives memory usage up. Fix the query first. - "Increase
tmp_table_size": only useful if on-disk temporary tables come from legitimate queries. Often the cause is aGROUP BYorORDER BYwithout a suitable index, or TEXT/BLOB columns forcing the switch to disk with the MEMORY engine (the case on MariaDB; MySQL's TempTable engine keeps them in memory since 8.0.13). - "Increase
table_open_cache": reasonable if the opened-tables counter climbs fast, but check the system's file descriptor limit at the same time.
Step 3: the few settings that really matter
innodb_buffer_pool_size
The most important setting on an InnoDB database: the memory where frequently read data and indexes live. The "70 to 80% of RAM" rule only holds for a server dedicated to the database. The right question is: what share of the data is read regularly, and how much memory is left once the OS, per-connection buffers and other services are accounted for? If the active dataset fits in memory, reads barely touch the disk.
Redo log size
A redo log that's too small forces InnoDB to flush pages to disk too often under heavy write load. Since MySQL 8.0.30, you set innodb_redo_log_capacity, which can be changed live (innodb_log_file_size is deprecated there). On MariaDB, innodb_log_file_size remains the setting. Size it to absorb write peaks, not at random.
innodb_flush_log_at_trx_commit
At 1 (default), every commit is written and synced to disk: no committed transaction is lost on a crash, provided the storage honours syncs (and with sync_binlog=1 if the binlog is enabled). At 2, write throughput improves, but a power cut can lose roughly one second of transactions, sometimes more: it's not a guaranteed bound. That's a business decision, not a free optimisation.
max_connections
Raising max_connections to make "Too many connections" go away treats the symptom. Each connection can consume its own buffers: 1,000 concurrent connections can exhaust RAM. The real question: why are so many connections open? Slow queries piling up, a badly tuned application connection pool, no connection proxy.
Step 4: queries and indexes, where the real gain is
Once the expensive queries are identified, EXPLAIN shows the plan chosen by the optimizer: index used or not, estimated row count, temporary table, sort. EXPLAIN ANALYZE (MySQL 8.0.18+) and ANALYZE (MariaDB) go further: they run the query and compare the estimate with reality. A large gap between estimated and actual rows points to stale statistics or a poorly chosen index.
Analyse a query and spot useless indexes
-- MySQL 8.0.18+: runs the query and times each step
EXPLAIN ANALYZE SELECT ...;
-- MariaDB: same idea, actual vs estimated plan
ANALYZE FORMAT=JSON SELECT ...;
-- MySQL, MariaDB 10.6+ (performance_schema enabled): indexes with no observed use
SELECT * FROM sys.schema_unused_indexes;
-- MariaDB without performance_schema: userstat can be enabled live,
-- indexes missing from INDEX_STATISTICS after a full cycle are suspects
SET GLOBAL userstat = 1;
SELECT * FROM information_schema.INDEX_STATISTICS;The sys schema, available on MySQL and bundled with MariaDB since 10.6, provides ready-made views: indexes with no observed use (candidates for removal after a full activity cycle, and only if no constraint relies on them), full-scan queries, the busiest tables. They rely on performance_schema, disabled by default on MariaDB: enabling it requires a restart. On older versions, the same information lives in performance_schema, with more effort.
Typical fixes: add a composite index with the right column order, rewrite an OR or a correlated subquery, avoid a function applied to an indexed column in the WHERE, paginate without a huge OFFSET. On a large production table, adding an index needs preparation: an online schema change tool, a test on a copy.
Step 5: what if it isn't the database?
- The disk: high I/O latency, saturated shared network storage, a filling disk.
iostat -xoften tells you more than any MySQL ratio. - The application: the "N+1" problem, one query per displayed row, fires 500 small fast queries instead of one. None shows up in the slow query log, yet the page takes 3 seconds.
- Locks: a transaction left open by the application keeps its locks and blocks queries that need incompatible locks.
SHOW ENGINE INNODB STATUSand the process list show who is waiting on whom. - Network and virtualisation: CPU stolen by the hypervisor, latency between application and database.
What an audit adds
This method is often enough to find the first gains. A MySQL / MariaDB audit runs it systematically on your real workload: slow query log analysis over a representative period, schema and index review, configuration read against your hardware, replication and backup checks, with optional Signal18 co-review.
The deliverable is a list of actions ranked by impact and risk, not a miracle configuration. If you'd rather delegate day-to-day operations, MariaDB / MySQL Remote DBA includes this follow-up over time.
Frequently asked questions
Is MySQLTuner reliable?
Its measurements are; its recommendations are leads. It reads global counters without knowing your queries or your application. Applying every suggestion without understanding each one can degrade performance or stability, as its authors point out themselves.
How long should you wait before running MySQLTuner?
At least 24 hours of uptime since the last restart, ideally a period covering a full activity cycle (daytime peaks, nightly jobs). On a freshly restarted server, its ratios are meaningless.
What value for long_query_time?
1 second is a good starting point in production. You can go down to 0.1 or 0.5 seconds for a one-off analysis, while watching the file size. At 0, the time threshold no longer filters anything (other settings, such as admin statements or min_examined_row_limit, still apply): useful for a few minutes, not continuously.
Should you enable the query cache to speed up MariaDB?
Rarely. It no longer exists in MySQL 8.0 and is disabled by default in MariaDB. It's invalidated on every write to the table concerned and creates contention as soon as the database writes a lot. It can help some read-mostly workloads; otherwise an application cache or better indexes are usually more effective. Measure it.
Your database is slowing down and you don't know where to start?
The audit analyses your real workload and hands you a list of actions ranked by impact and risk.
Start your managed MariaDB project
Let's discuss your database needs. Our DBA team advises you on the optimal architecture for your use case.
RDEM Systems SAS — SIREN 820 338 671 — 5 B rue des Noyers, 95300 Pontoise