Learn 🧠 All Concepts (20) 🤖 What is an LLM? 📚 RAG Explained ⚡ AI Agents 💻 Run AI Locally 🇮🇳 AI in India 📖 Learn Tracks 🔧 DevOps Track ⚙️ AI Ops Track 🗺️ AI Engineer Roadmap
Tools 🔧 AI Tools Directory 🔓 Open Source AI ⭐ Top GitHub Repos ✦ Claude Skill Repos 🚀 Ready-to-Deploy Projects
Build 🏗️ Build Hub 🎯 Master Prompts 🧩 RAG Agents 🚀 App Megaprompts
Workflows ⚡ All Workflows (22) 🎥 Text to Video 🎞️ Image to Video 🔊 Text to Speech ♻️ Automation
Resources 🧪 Colab Notebooks ⚙️ n8n Workflows 📈 Algo Trading 💰 Passive Income
🗂️ Browse All Topics About AItheGuru
Learn MySQL Priya cuts the database bill by 60 percent
MySQL Ch 15 / 18 Expert
💰

Priya cuts the database bill by 60 percent

Unused indexes, fragmentation, connection pooling, Redis caching, my.cnf tuning

⏱ 14 min 6 commands 5 takeaways
💰
In this chapter
Priya
DBA, reducing cloud database costs
The story

Priya's manager came to her: the database is getting expensive. We are spending 80,000 rupees a month on RDS. Can you optimise it without reducing features?

She did a full audit. She found bloated tables, missing indexes, and a buffer pool that was too small. She reduced costs by 60% in two weeks.

FINDING WASTED SPACE

-- Table sizes:
SELECT
    table_name,
    ROUND((data_length + index_length) / 1024 / 1024, 2) AS size_mb,
    ROUND(data_free / 1024 / 1024, 2) AS free_mb,
    table_rows
FROM information_schema.TABLES
WHERE table_schema = 'myapp_db'
ORDER BY size_mb DESC;
-- data_free is wasted space from deletes -- reclaim it:
OPTIMIZE TABLE orders;   -- reclaims fragmented space (locks the table briefly)
-- Check index sizes:
SELECT
    table_name, index_name,
    ROUND(stat_value * 16 / 1024, 2) AS size_mb
FROM mysql.innodb_index_stats
WHERE database_name = 'myapp_db'
AND stat_name = 'size'
ORDER BY size_mb DESC;

FINDING UNUSED INDEXES

-- Indexes that MySQL has never used:
SELECT object_schema, object_name, index_name
FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE index_name IS NOT NULL
AND count_star = 0
AND object_schema = 'myapp_db'
ORDER BY object_name, index_name;
-- DROP indexes that are never used -- they only slow down writes

QUERY CACHE AND PREPARED STATEMENTS

-- Prepared statements are more efficient for repeated queries:
PREPARE stmt FROM 'SELECT * FROM users WHERE id = ?';
SET @id = 42;
EXECUTE stmt USING @id;
DEALLOCATE PREPARE stmt;
-- In application code (Python example):
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
-- The database engine caches the query plan for prepared statements

CONNECTION POOLING

Without connection pooling, your app opens a new database connection for every request. Opening a connection takes 50-100ms. With 1000 requests/second that is the main bottleneck.

-- Check current connections:
SHOW STATUS LIKE 'Threads_connected';
SHOW STATUS LIKE 'Max_used_connections';
-- In your app, use a connection pool:
-- Python: use SQLAlchemy's pool_size=10, max_overflow=20
-- Node.js: use mysql2 with connectionLimit: 10
-- Java: use HikariCP with maximumPoolSize: 10
-- Right-size max_connections in my.cnf:
-- Formula: (RAM_for_connections) / (RAM_per_connection)
-- Each MySQL connection uses about 4-16MB
-- With 8GB RAM for connections: max ~500 connections

QUERY RESULT CACHING WITH REDIS

For queries that are called 1000 times/second but data changes once per minute, cache the result in Redis:

-- Instead of hitting MySQL 1000 times:
SELECT city, COUNT(*) FROM users GROUP BY city;
-- Hit MySQL once, cache in Redis for 60 seconds:
-- Python example:
-- cache_key = 'users_by_city'
-- cached = redis.get(cache_key)
-- if not cached:
--     result = cursor.execute("SELECT city, COUNT(*) FROM users GROUP BY city")
--     redis.setex(cache_key, 60, json.dumps(result))
-- Which queries to cache:
-- Dashboard counts and aggregates
-- Lookup tables that rarely change (cities, categories, product types)
-- Queries that are slow AND called frequently

TUNING my.cnf FOR PRODUCTION

[mysqld]
# Most important: 70-80% of RAM for InnoDB buffer pool
innodb_buffer_pool_size = 12G       # on a 16GB server
innodb_buffer_pool_instances = 8    # one per GB of buffer pool, max 8
# Reduce disk I/O (set to 2 for safety, 0 for speed):
innodb_flush_log_at_trx_commit = 1  # 1=safe, 2=faster
# Log file size (larger = faster writes, longer recovery):
innodb_log_file_size = 256M
# Connection limits:
max_connections = 200
wait_timeout = 600       # close idle connections after 10 minutes
interactive_timeout = 600
# Query cache (MySQL 8 removed it -- use Redis instead):
slow_query_log = 1
long_query_time = 0.5     # log queries over 0.5 seconds

Priya's optimisation steps saved 48,000 rupees per month. The wins: dropped 11 unused indexes (faster writes), reclaimed 40GB of fragmented space (smaller backups), increased buffer pool size (fewer disk reads), added Redis for dashboard queries (less CPU on MySQL).

Key takeaways

performance_schema.table_io_waits_summary_by_index_usage shows indexes with count_star = 0 — these unused indexes only slow down writes

data_free in information_schema.TABLES shows wasted space from deletes — OPTIMIZE TABLE reclaims it

innodb_buffer_pool_size should be 70-80% of total RAM — the single most impactful setting for a dedicated database server

Connection pools reuse connections instead of creating new ones per request — creating a connection takes 50-100ms

Cache stable aggregate queries in Redis with a short TTL — a dashboard query called 1000x/second belongs in cache, not in MySQL

Commands from this chapter
$ SELECT table_name, ROUND((data_length+index_length)/1024/1024,1) AS mb, ROUND(data_free/1024/1024,1) AS free_mb FROM information_schema.TABLES WHERE table_schema='myapp_db' ORDER BY mb DESC;
Find largest tables and wasted space
$ SELECT object_name, index_name FROM performance_schema.table_io_waits_summary_by_index_usage WHERE count_star = 0 AND object_schema = 'myapp_db';
Find unused indexes to drop
$ OPTIMIZE TABLE orders;
Reclaim fragmented space from deleted rows
$ SHOW STATUS LIKE 'Threads_connected'; SHOW STATUS LIKE 'Max_used_connections';
Check connection usage
$ SHOW VARIABLES LIKE 'innodb_buffer_pool_size';
Check current buffer pool size
$ PURGE BINARY LOGS BEFORE DATE_SUB(NOW(), INTERVAL 3 DAY);
Remove old binary logs to free disk space