When MySQLTuner reports your maximum possible memory usage exceeds physical RAM (153% in your case), it's calculating:
Global buffers + (Per-thread buffers × max_connections)
In your 256MB VPS, the current configuration could theoretically consume 392.9MB - a guaranteed path to thrashing.
Your current global buffers (338M) are shockingly high for a 256MB system. The main offenders:
query_cache_size = 64M # Overkill for small VPS key_buffer = 8M # Too small for your 9.4M indexes table_cache = 128 # Each consumes memory
Each connection consumes:
thread_stack (128K) + sort_buffer (default 2M) + join_buffer (256K) + other per-connection buffers
With max_connections=20, this becomes dangerous quickly.
Immediate changes for your my.cnf:
[mysqld] key_buffer_size = 12M # Cover all MyISAM indexes query_cache_size = 8M # Drastic reduction table_open_cache = 64 # Halve the cache thread_cache_size = 4 # Reduce thread overhead max_connections = 10 # Critical reduction tmp_table_size = 16M # From insane 256M max_heap_table_size = 16M # Match tmp_table_size join_buffer_size = 128K # Minimum effective size
Beyond configuration, execute these commands:
# Defragment tables (from MySQLTuner warning): mysql -e "SELECT CONCAT('OPTIMIZE TABLE ', table_schema, '.', table_name, ';') FROM information_schema.tables WHERE data_free > 0 AND engine='MyISAM'" | mysql # Monitor real memory usage: watch -n 5 "ps -eo pmem,pcpu,rss,vsize,args | grep mysqld"
For applications you control, implement connection pooling. Example PHP snippet:
$mysqli = new mysqli('p:localhost', 'user', 'pass', 'db'); // 'p:' enables persistent connections
Consider installing ProxySQL to manage connection spikes:
# Installation (Ubuntu): sudo apt-get install proxysql # Configure in /etc/proxysql.cnf: max_connections=100 # Proxy handles spikes mysql-max_connections=5 # Real MySQL connections
Create a monitoring script (save as mysql_mem.sh):
#!/bin/bash while true; do mysql -e "SHOW STATUS LIKE 'Threads_connected';" free -m sleep 5 done
After changes, your target metrics should show:
Maximum possible memory usage ≤ 200M (78% of RAM) Threads_connected averaging ≤ 5 Key buffer hit rate ≥ 95%
Your MySQLTuner report shows alarming memory consumption (392.9M) exceeding your 256M VPS capacity. Let's decode how MySQL calculates this:
Maximum Memory Usage = Global Buffers + (Per-thread Buffers * max_connections) + (OS/Application Overhead)
Examining your my.cnf reveals several optimizations needed:
# Current problematic settings key_buffer = 8M # Too small for 9.4M indexes query_cache_size = 64M # Excessive for read-heavy workload tmp_table_size = 256M # Dangerous for 256M VPS max_heap_table_size = 256M # Matches tmp_table_size (good) but too large max_connections = 20 # High for low-memory system
Immediate Memory Reductions
Add these to your my.cnf under [mysqld] section:
key_buffer_size = 10M # Slightly larger than total index size query_cache_size = 16M # Reduced from 64M tmp_table_size = 16M # Drastically reduced max_heap_table_size = 16M # Must match tmp_table_size table_open_cache = 64 # Reduced from 128 thread_cache_size = 4 # Reduced from 8 max_connections = 10 # Halved from 20
Query Cache Optimization
For your read-heavy workload (100% reads), monitor cache efficiency:
SHOW STATUS LIKE 'Qcache%'; SHOW VARIABLES LIKE 'query_cache%';
Consider disabling completely if efficiency remains low:
query_cache_type = 0 query_cache_size = 0
Address the 34 fragmented tables reported by MySQLTuner:
# Generate optimization commands for all MyISAM tables SELECT CONCAT('OPTIMIZE TABLE ', table_schema, '.', table_name, ';') FROM information_schema.tables WHERE engine = 'MyISAM' AND table_schema NOT IN ('information_schema','mysql');
Instead of high max_connections, implement connection pooling:
# Example PHP PDO connection pooling $options = [ PDO::ATTR_PERSISTENT => true, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION ]; $dbh = new PDO('mysql:host=localhost;dbname=test', $user, $pass, $options);
Use this query to track real memory usage:
SELECT @@key_buffer_size + @@query_cache_size + @@tmp_table_size + (@@read_buffer_size + @@sort_buffer_size + @@join_buffer_size + @@thread_stack) * @@max_connections AS estimated_max_memory;
For pure MyISAM environments (no InnoDB), add these specialized settings:
myisam_sort_buffer_size = 2M concurrent_insert = 2 bulk_insert_buffer_size = 1M