
Table of Contents
MySQL and MariaDB are the most widely used databases on VPS servers, but their default configurations are designed for low-memory machines — not modern cloud VPS environments. This guide walks you through the most impactful tuning steps to maximize database performance.
Why Is MySQL Slow
Understanding the root cause before tuning is critical. Most out-of-the-box MySQL installations suffer from these common issues:
| Problem | Root Cause | Impact |
|---|---|---|
| innodb_buffer_pool_size too small | Default is only 128MB | Constant disk reads, very slow |
| max_connections set too high | Pre-allocates RAM per connection | RAM exhaustion, heavy swapping |
| Missing indexes | No EXPLAIN analysis done | Full table scans on every query |
| No slow query logging | Not enabled by default | Can't identify bottleneck queries |
| Binary logging enabled unnecessarily | Some distro defaults | Wasted disk I/O |
Tuning InnoDB Buffer Pool
The InnoDB buffer pool is the single most important MySQL tuning parameter. It caches table data and indexes in RAM, avoiding expensive disk reads. The larger it is, the better — up to the limit of available RAM.
Formula: Set innodb_buffer_pool_size to 50–70% of total RAM. Reserve the rest for the OS, PHP-FPM, and Nginx/Apache.
| VPS RAM | Recommended Value |
|---|---|
| 2 GB | 1G |
| 4 GB | 2G–2.5G |
| 8 GB | 4G–5G |
| 16 GB | 8G–10G |
Check current buffer pool usage:
mysql -u root -p -e "SHOW VARIABLES LIKE 'innodb_buffer_pool_size';"
mysql -u root -p -e "SHOW STATUS LIKE 'Innodb_buffer_pool_reads';"
Key my.cnf Settings
The main MySQL/MariaDB config file is at /etc/mysql/my.cnf or /etc/my.cnf depending on your distro. Here's a well-tuned starting configuration:
[mysqld]
# Buffer Pool — adjust based on available RAM
innodb_buffer_pool_size = 2G
innodb_buffer_pool_instances = 2
# Log File — larger = fewer checkpoints
innodb_log_file_size = 256M
innodb_log_buffer_size = 16M
# I/O Settings
innodb_flush_log_at_trx_commit = 2 # Reduce I/O (loses ~1s data on crash)
innodb_flush_method = O_DIRECT # Avoid OS double buffering
# Connections
max_connections = 150 # Reduce from default 151-200
thread_cache_size = 16
# Query Cache (MariaDB only — removed in MySQL 8)
# query_cache_type = 1
# query_cache_size = 64M
# query_cache_limit = 2M
# Tmp Tables — keep in RAM
tmp_table_size = 64M
max_heap_table_size = 64M
# Slow Query Log
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 2
# Binary Log — disable if not using replication
# skip-log-bin
⚠️ MySQL 8.0 users: query_cache was completely removed. Do NOT add query_cache directives — MySQL will refuse to start.
After editing, restart MySQL:
sudo systemctl restart mysql
# or for MariaDB
sudo systemctl restart mariadb
Enabling Slow Query Log
The slow query log is your most powerful tool for identifying bottleneck queries. Enable it before you start tuning, then analyze after a few hours of production traffic.
# Check current status
SHOW VARIABLES LIKE 'slow_query_log';
SHOW VARIABLES LIKE 'long_query_time';
# Enable without restart
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 2;
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';
Analyze with mysqldumpslow:
# Show 10 slowest queries by total time
mysqldumpslow -s t -t 10 /var/log/mysql/slow.log
# Show most frequently executed slow queries
mysqldumpslow -s c -t 10 /var/log/mysql/slow.log
Index and Query Optimization
Good indexes can speed up queries by 100–1000x. Use EXPLAIN to verify your queries are using indexes efficiently.
-- Check query execution plan
EXPLAIN SELECT * FROM orders WHERE user_id = 123 AND status = 'pending';
-- Show existing indexes
SHOW INDEX FROM orders;
-- Add a composite index
ALTER TABLE orders ADD INDEX idx_user_status (user_id, status);
-- Remove an unused index
DROP INDEX old_index_name ON orders;
Tip: In EXPLAIN output, a type value of ALL means a full table scan. Add an appropriate index immediately.
Find unused indexes
-- MySQL 8 / MariaDB 10.6+
SELECT * FROM sys.schema_unused_indexes
WHERE object_schema NOT IN ('performance_schema','mysql','sys','information_schema');
Using MySQLTuner
MySQLTuner automatically analyzes your MySQL/MariaDB installation and recommends tuning values. Run it after at least 24 hours of production traffic for meaningful recommendations.
# Download
wget https://raw.githubusercontent.com/major/MySQLTuner-perl/master/mysqltuner.pl
# Run
perl mysqltuner.pl --user root --pass your_password
The output highlights:
- [!!] — Critical issues requiring immediate attention
- [OK] — Parameters already well-configured
- Variables to adjust — Recommended tuning values with specific numbers
MariaDB vs MySQL
For new VPS deployments, MariaDB is worth considering over MySQL:
| Feature | MySQL 8.0 | MariaDB 10.11 |
|---|---|---|
| Query Optimizer | Good | Better in many workloads |
| Query Cache | Removed | Available (optional) |
| JSON Support | Native JSON type | Dynamic columns + JSON |
| Storage Engines | InnoDB, MyISAM | InnoDB, Aria, ColumnStore |
| Galera Cluster | Requires plugin | Built-in support |
| License | GPL/Commercial | GPL (fully open) |
Performance Monitoring
After tuning, monitor continuously to validate improvements:
# Real-time status
mysqladmin -u root -p status
# Active processes
SHOW PROCESSLIST;
# Key global status counters
SHOW GLOBAL STATUS LIKE 'Questions';
SHOW GLOBAL STATUS LIKE 'Slow_queries';
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_reads';
SHOW GLOBAL STATUS LIKE 'Threads_connected';
SHOW GLOBAL STATUS LIKE 'Aborted_connects';
Calculate Buffer Pool Hit Rate
-- Should be above 99%
SELECT
(1 - (Innodb_buffer_pool_reads / Innodb_buffer_pool_read_requests)) * 100
AS buffer_pool_hit_rate
FROM (
SELECT VARIABLE_VALUE AS Innodb_buffer_pool_reads
FROM information_schema.GLOBAL_STATUS
WHERE VARIABLE_NAME = 'Innodb_buffer_pool_reads'
) r,
(
SELECT VARIABLE_VALUE AS Innodb_buffer_pool_read_requests
FROM information_schema.GLOBAL_STATUS
WHERE VARIABLE_NAME = 'Innodb_buffer_pool_read_requests'
) rr;
Optimization Checklist
| Item | Action |
|---|---|
| innodb_buffer_pool_size | Set to 50-70% of total RAM |
| slow_query_log | Enable with long_query_time = 2 |
| max_connections | Reduce to match actual load |
| Indexes | Run EXPLAIN on key queries |
| tmp_table_size | Set to 64M to reduce disk tmp tables |
| MySQLTuner | Run after 24h and apply recommendations |
| binary log | Disable if not using replication |
| Buffer Pool Hit Rate | Must be above 99% |
FAQ
Why is MySQL slow on my VPS?
MySQL defaults are designed for low-RAM machines, not modern VPS servers. The most impactful fix is increasing innodb_buffer_pool_size to 50-70% of available RAM, which caches data in memory instead of reading from disk constantly.
What should I set innodb_buffer_pool_size to?
Set it to 50-70% of total RAM. For a 4GB VPS, use innodb_buffer_pool_size = 2G. Leave enough RAM for the OS, PHP, and your web server to avoid excessive swapping.
What is the slow query log and how does it help?
The slow query log records queries that take longer than a threshold (long_query_time). By analyzing it with mysqldumpslow, you can identify queries that need better indexes or rewrites, directly targeting the root cause of slowness.
Should I use MariaDB instead of MySQL on my VPS?
MariaDB is a drop-in replacement for MySQL with a generally faster optimizer, built-in Galera Cluster support, and an active open-source community. For new VPS setups, MariaDB is a solid choice, especially on Ubuntu/Debian where it integrates well.
How do I check the InnoDB buffer pool hit rate?
Run SHOW GLOBAL STATUS for Innodb_buffer_pool_reads and Innodb_buffer_pool_read_requests, then calculate (1 - reads/read_requests) * 100. It should be above 99% for a well-tuned server.
Do I need to restart MySQL after editing my.cnf?
Most settings require a MySQL restart. Some can be changed live with SET GLOBAL, but those changes are lost on restart. Always update my.cnf first, then restart to make changes permanent.