Indexes are B+ trees that reduce row scans from full-table to log(n) lookups
EXPLAIN shows query execution plan — check type, key, rows and Extra columns
Buffer pool caches data pages in memory; setting it too small causes constant disk reads
Query optimizer picks a plan based on stats — stale statistics kill performance
Production rule: never deploy without EXPLAIN and slow query log enabled
Plain-English First
Imagine a library with a million books but no card catalogue. Every time someone asks for a book, a librarian walks every single aisle checking every shelf. That's MySQL without indexes — it reads every row to find what you want. Performance tuning is the art of giving MySQL better catalogues, bigger reading desks, and smarter librarians so it finds answers in seconds instead of hours. The difference between a 200ms query and a 4-second query on the same data is almost always about how well you've tuned these three things.
At some point, every production MySQL database hits a wall. Traffic grows, tables balloon past 50 million rows, and suddenly that dashboard query that used to be instant is timing out. Your users notice before your monitoring does. This isn't bad luck — it's physics. MySQL is doing exactly what you told it to, just with more data than your original design anticipated. The engineers who get promoted are the ones who saw it coming and knew exactly which levers to pull.
What is MySQL Performance Tuning?
Performance tuning means making MySQL use its resources efficiently: memory to cache data, CPU to execute queries fast, and disk only when necessary. The goal is to minimize query latency and maximize throughput. This involves three pillars: indexing strategies, query optimization, and InnoDB configuration.
You'll know you need tuning when a query that worked fine with 1 million rows suddenly crawls past 10 million. The solution is never just "add more hardware" — at least not until you've exhausted your tuning options.
performance_check.sqlSQL
1
2
3
4
5
6
-- TheCodeForge — check current buffer pool hit ratioSELECT
(Innodb_buffer_pool_read_requests - Innodb_buffer_pool_reads)
/ Innodb_buffer_pool_read_requests * 100AS buffer_pool_hit_ratio
FROM performance_schema.global_status
WHERE variable_name IN ('Innodb_buffer_pool_read_requests', 'Innodb_buffer_pool_reads');
Output
buffer_pool_hit_ratio: 99.4
Forge Tip:
Type this code yourself rather than copy-pasting. The muscle memory of writing it will help it stick.
Production Insight
A buffer pool hit ratio below 99% means your working set doesn't fit in memory.
First tuning step: increase innodb_buffer_pool_size to 70-80% of dedicated server RAM.
If that's already done, your queries are scanning too much data — fix the indexes.
Key Takeaway
Indexes, query design, and cache are the three levers of MySQL tuning.
Fix indexes first — they give the biggest bang for your time.
After that, look at the buffer pool, then the query itself.
Tuning path decision tree
IfSlow query, no index in EXPLAIN
→
UseAdd index — see Indexes section
IfSlow query, index exists but not used
→
UseCheck stats, rewrite query, or analyze table
IfHigh disk I/O, good index usage
→
UseIncrease buffer pool size or tune cache settings
How Indexes Actually Work in InnoDB
Your index is a B+ tree stored away from the table's data rows. Every query that uses an index walks this tree — root to leaf — in log(n) steps. That's why a query scanning 10 million rows with an index might only read 20 tree nodes.
InnoDB uses a clustered index on the primary key — meaning the table data itself is stored in the B+ tree order. Secondary indexes store a copy of the indexed column(s) plus the primary key value, so a lookup via a secondary index requires two tree walks: first to find the primary key, then to the clustered index.
When you add an index, you're trading write overhead for read speed. Each INSERT or UPDATE must update every relevant index. That's why you don't want indexes on columns you never filter or sort by.
create_index.sqlSQL
1
2
3
4
5
6
7
8
9
10
-- TheCodeForge — add index and check sizeALTERTABLE orders ADDINDEXidx_customer_id (customer_id);
-- Check index sizeSELECT
table_name,
index_name,
stat_value * @@innodb_page_size AS index_size_bytes
FROM mysql.innodb_index_stats
WHERE table_name = 'orders'AND stat_name = 'size';
Output
orders | idx_customer_id | 10485760
B+Tree Mental Model
Root node: points to the right subsection
Internal nodes: keep dividing the name range
Leaf nodes: contain the actual phone numbers (or primary keys)
Every leaf level is a linked list — range scans just walk forward
Deleting a node is rare; the tree stays balanced automatically
Production Insight
A SELECT without a WHERE will always do a full index scan or table scan — indexes on unselective columns (like boolean flags) rarely help.
Composite indexes must match leftmost prefix: idx(a,b,c) covers a and a,b but not b or b,c.
Rule: order columns by equality first, then range, then sort in your queries.
Key Takeaway
Indexes are B+trees that trade write speed for read speed.
Secondary indexes need two tree walks — keep them narrow.
To not index is the default cause of slow queries.
Index choice decision tree
IfColumn used in WHERE with high selectivity
→
UseSingle-column B-tree index
IfMultiple columns in WHERE/JOIN/ORDER BY
→
UseComposite index — order by equality, range, sort
IfQuery covers all needed columns
→
UseCovering index (includes all SELECT columns)
Reading Execution Plans with EXPLAIN
EXPLAIN shows you how MySQL will execute a query. The key fields are type, key, rows, and Extra. type should ideally be ref, eq_ref, or const — not ALL (full table scan) or index (full index scan).
rows is an estimate — it tells you how many rows MySQL expects to examine. If that number is close to the total table size, you're scanning everything. Extra often reveals hidden work: "Using filesort" means MySQL had to sort in temp memory, "Using temporary" means it created a temp table (often for GROUP BY or DISTINCT).
Always run EXPLAIN on new queries before deployment. Also run it when query performance regresses — the plan can change after table data grows or statistics update.
explain_plan.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
12
-- TheCodeForge — analyze a slow queryEXPLAINFORMAT=JSONSELECT o.*, c.name
FROM orders o
JOIN customers c ON o.customer_id = c.id
WHERE o.order_date >= '2025-01-01'AND o.total_amount > 100;
-- Look for:-- "access_type": "ALL" on orders => full scan-- "possible_keys": NULL => no usable index-- "rows_examined_per_scan": large => too many rows
Output
{
"query_block": {
"select_id": 1,
"table": {
"table_name": "orders",
"access_type": "ALL",
"rows_examined_per_scan": 10000000,
"filtered": 10,
"attached_condition": "..."
}
}
}
Common EXPLAIN Traps
rows is an estimate, not exact. The optimizer can be off by 10x after table changes. Always run ANALYZE TABLE if you suspect stale stats.
Also, a plan that uses an index but still examines many rows might be doing an index scan (type=index) — almost as bad as a table scan.
Production Insight
The optimizer cost model doesn't account for spinning vs SSD in older MySQL versions.
A plan that looks cheap on paper might do random I/O that thrashs HDDs.
Upgrade to MySQL 8.0 for better cost constants and histogram support.
Key Takeaway
EXPLAIN is the first thing you check when a query goes from fast to slow.
type=ALL means start a post-mortem.
rows_examined is the number that matters.
EXPLAIN analysis decision tree
Iftype=ALL (full table scan)
→
UseMissing index — create one on the WHERE column(s)
Iftype=index (full index scan)
→
UseIndex exists but unselective — consider adding columns to WHERE or use covering index
Iftype=ref or eq_ref
→
UseGood — index used efficiently. Check rows estimate for accuracy
Tuning the InnoDB Buffer Pool
The InnoDB buffer pool caches table and index data in memory. When a page is requested, InnoDB checks the buffer pool; if missing, it reads from disk (buffer pool miss). Tuning it is often the second step after indexing.
The buffer pool size should be as large as possible without causing swapping. On a dedicated MySQL server, 70-80% of RAM is a safe starting point. Use innodb_buffer_pool_size to set it, and innodb_buffer_pool_instances to reduce contention on large pools (each instance manages its own buffer chunk).
Monitor hit ratio: you want >99%. A drop below 99% means your active data set no longer fits. Either increase the pool, or query fewer rows per statement.
buffer_pool_check.sqlSQL
1
2
3
4
5
-- TheCodeForge — find your ideal buffer pool sizeSELECTCEILING(SUM(data_length + index_length) / 1024 / 1024) AS estimated_mb
FROM information_schema.tables
WHERE table_schema = 'your_database';
Output
estimated_mb: 2048
Buffer Pool Strategy
On a multi-instance setup, give each instance its own buffer pool. Don't share memory between MySQL and other services on the same box — use cgroups or virtualization if you must.
Production Insight
A spike in miss rate often follows a full table scan of a large table that evicts hot pages.
This causes temporary performance degradation for all active queries.
Consider setting innodb_old_blocks_time to prevent scan pollution.
Key Takeaway
Buffer pool is the second lever after indexing.
99% hit ratio is the target.
If hit ratio drops, your active dataset no longer fits in memory.
Buffer pool sizing decision tree
IfHit ratio >99%
→
UsePool size is adequate — no action needed
IfHit ratio <99% and free RAM available
→
UseIncrease innodb_buffer_pool_size
IfHit ratio <99% but no free RAM
→
UseReduce working set: add indexes, limit per-query scans, partition large tables
Query Optimizer Gotchas
The MySQL query optimizer makes execution plan decisions based on statistics about tables and indexes. If statistics are stale, you'll get a bad plan.
Stale statistics happen after large INSERT, UPDATE, or DELETE operations — especially bulk loads. The optimizer doesn't automatically recompute index cardinality. You must run ANALYZE TABLE.
Another common gotcha: the optimizer may choose not to use an index if it estimates that reading the index plus the rows would be more expensive than a full table scan. This happens for low selectivity or very small tables.
Also, MySQL's optimizer doesn't always handle OR conditions well. Sometimes rewriting with UNION can force a better plan.
analyze_table.sqlSQL
1
2
3
4
5
6
-- TheCodeForge — refresh statistics and check plan changeANALYZETABLE orders;
EXPLAINSELECT * FROM orders WHERE customer_id = 123;
-- Compare before/after: type, rows, and Extra should improve.
Output
Output: type changes from ALL to ref, rows decreases from 10M to 150.
Plan Stability
In production, use optimizer hints (e.g., FORCE INDEX) sparingly — they can prevent plan changes that would be better after data growth. Better to fix the underlying issue: missing index or outdated stats.
Production Insight
A query plan can change after a dump/restore if table statistics are not transferred.
Always run ANALYZE TABLE after importing data.
We've seen 10x regression because a restored database had no statistics at all.
Key Takeaway
Stale stats are the silent killer of query performance.
Run ANALYZE TABLE after any bulk data change.
When a query regresses, check the plan first — not the hardware.
Optimizer gotcha decision tree
IfPlan changed after bulk load
→
UseRun ANALYZE TABLE — likely stale stats
IfOptimizer ignores index on low-selectivity column
→
UseCheck selectivity; if <20% unique, index may not be used — consider covering index or rewrite query
IfOR conditions cause poor plan
→
UseRewrite as UNION or use index merge hints
Monitoring and Profiling Queries
You can't fix what you don't measure. Enable the slow query log with long_query_time=2 (captures queries >2 seconds). Use pt-query-digest or mysqldumpslow to analyze the log.
Performance Schema provides fine-grained metrics: wait events, stage events, and statement events. It's enabled by default in MySQL 8.0. Use sys schema to get human-readable summaries.
For real-time profiling, use SHOW PROFILE (deprecated in 8.0 but still works) or the performance_schema. Re-enable it per connection.
Set up automated alerts for query time outliers — don't wait for users to complain.
DIGEST_TEXT: SELECT * FROM orders WHERE customer_id = ?
SUM_TIMER_WAIT: 12000000000
COUNT_STAR: 500
Profiling Tip
In MySQL 8.0, performance_schema is your best friend. Enable it by default and use sys schema helper: CALL sys.create_histogram? No — just use sys.statement_performance tables.
Production Insight
Don't enable slow query log with long_query_time=0 in high-traffic production — it'll flood disk and affect performance.
Start with 2 seconds, then lower to 1 after you've resolved the worst offenders.
Use pt-query-digest to aggregate patterns instead of reading raw logs.
Key Takeaway
You need metrics before tuning.
Enable slow query log and performance schema from day one.
Automate the analysis — humans don't read logs at 3 AM.
Beyond the buffer pool, a handful of MySQL configuration parameters can make or break your production workload. innodb_log_file_size controls the redo log capacity — too small causes frequent checkpoint flushes that stall writes. sort_buffer_size and join_buffer_size are per-session buffers; setting them too high globally can eat memory fast. max_connections needs to balance against thread stack and connection overhead.
The key is knowing which parameters are global and which are session-level. Global changes require a restart, so tune them in a staging environment first. Session-level settings like sort_buffer_size can be set per query with SET SESSION — useful for batch jobs without affecting normal traffic.
config_check.sqlSQL
1
2
3
4
5
-- TheCodeForge — review current configurationSHOWVARIABLESLIKE'innodb_log_file_size';
SHOWVARIABLESLIKE'max_connections';
SHOWVARIABLESLIKE'innodb_flush_log_at_trx_commit';
SHOWSTATUSLIKE 'Innodb_log_waits'; -- nonzero means log contention
Output
innodb_log_file_size: 268435456
max_connections: 151
innodb_flush_log_at_trx_commit: 1
Innodb_log_waits: 0
Common Configuration Trap
Setting innodb_flush_log_at_trx_commit = 2 improves write performance but trades durability. If you lose power, transactions from the last second may vanish. Know your durability requirements before changing this.
Production Insight
InnoDB log waits (Innodb_log_waits > 0) indicate the redo log is too small or the disk can't keep up.
Doubling the log size often eliminates the waits immediately.
But too large a log can slow crash recovery — balance with your RTO.
Key Takeaway
Tune per-session buffers and global limits separately.
Redo log size is a common bottleneck for write-heavy workloads.
Test configuration changes in a non-production environment first.
Configuration tuning decision tree
IfInnodb_log_waits > 0
→
UseIncrease innodb_log_file_size or number of log files
IfSlow writes but high InnoDB log free space
→
UseCheck innodb_flush_log_at_trx_commit — lower to 2 if acceptable
IfFrequent connection refusals
→
UseIncrease max_connections, but monitor memory per connection
Schema Optimization for Performance
Performance doesn't start with queries — it starts with schema design. Using appropriate data types (INT vs BIGINT, CHAR vs VARCHAR) reduces index size and memory pressure. Normalization avoids duplicate data but can cause JOIN overhead; denormalization speeds reads at the cost of write complexity. Choose based on access patterns.
Partitioning large tables can improve query isolation: for example, range-based partitioning on order_date allows partition pruning, scanning only relevant partitions. But too many partitions can degrade DDL performance.
Using covering indexes — where the index includes all columns needed by a query — eliminates the need for a second lookup (the clustered index). This is the fastest access method after primary key lookup.
covering_index.sqlSQL
1
2
3
4
5
-- TheCodeForge — create a covering index to avoid back-to-table-- Query: SELECT customer_id, order_date, total FROM orders WHERE customer_id = 123;CREATEINDEX idx_customer_covering ONorders (customer_id, order_date, total);
-- Now EXPLAIN shows 'Using index' in Extra — no table access needed.
Output
EXPLAIN EXTRA: 'Using index'
Schema Design Insight
Use spatial or full-text indexes only when the query pattern requires them. They have higher maintenance cost. For simple filtering, B-tree is still king.
Production Insight
A poorly chosen primary key, like UUID, causes index fragmentation and insertion bottlenecks.
Use sequential integers or ULID if UUIDs are unavoidable.
We've seen 40% write throughput loss from random primary keys.
Key Takeaway
Schema design decisions echo through every query.
Use appropriate data types and covering indexes.
Partitioning helps range queries but adds management overhead.
Schema design decision tree
IfQuery accesses a few columns from a wide table
→
UseConsider covering index on those columns
IfTable has millions of rows and range WHERE on date
→
UseConsider range partitioning on that date column
IfPrimary key is UUID, causing fragmentation
→
UseSwitch to BIGINT AUTO_INCREMENT or ULID stored as BINARY(16)
Advanced Indexing Techniques: Covering Indexes and Index Hints
Covering indexes include all columns a query needs, so MySQL can return results without touching the table at all. EXPLAIN will show 'Using index' in Extra. This avoids the second tree walk from secondary to clustered index, cutting I/O in half for many queries.
Index hints like FORCE INDEX tell the optimizer to use a specific index, but they're a temporary fix. Better to make the optimizer's default choice optimal by maintaining accurate statistics and designing appropriate composite indexes.
MySQL 8.0+ supports functional indexes — index on expressions like YEAR(order_date). Use them when WHERE clauses apply functions to columns. Without a functional index, MySQL can't use a regular index on order_date for a query like WHERE YEAR(order_date) = 2025.
advanced_index.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- TheCodeForge — covering index exampleCREATEINDEX idx_covering ONorders (customer_id, total, order_date);
-- Now this query reads only the index:SELECT customer_id, total, order_date FROM orders WHERE customer_id > 100;
-- Functional index in MySQL 8.0:CREATEINDEX idx_year_order ONorders ((YEAR(order_date)));
-- Query that benefits:SELECT * FROM orders WHEREYEAR(order_date) = 2025;
-- Use FORCE INDEX as a last resort:SELECT * FROM orders FORCEINDEX (idx_customer_id) WHERE customer_id = 42;
Output
EXPLAIN: Using index
Index Hint Trap
FORCE INDEX can cause a plan to use an index that's no longer optimal after data growth. Instead of hints, invest in proper composite indexes and updated statistics.
Production Insight
Covering indexes can reduce query time by 50-80% on read-heavy workloads.
But they increase index storage and write overhead — don't blindly index all columns.
Measure query patterns first, then design covering indexes for the top 10 slowest queries.
Key Takeaway
Covering indexes eliminate table lookups — fastest after PK lookup.
Use functional indexes for expressions.
Index hints are a crutch; fix the real problem.
Advanced indexing decision tree
IfQuery uses function on indexed column (e.g., YEAR(date))
→
UseCreate functional index on that expression
IfOptimizer still chooses wrong index after stats update
→
UseUse FORCE INDEX as temporary workaround, then redesign composite index
IfFrequent queries with same SELECT columns
→
UseConsider covering index to avoid table lookups
InnoDB Write Path: Redo Log, Checkpointing, and Flushing
When you update a row, InnoDB writes to the redo log first (sequential write) before modifying the buffer pool page. The redo log is a circular buffer of log files. A checkpoint writes dirty pages from the buffer pool to disk, advancing the checkpoint LSN.
If the redo log is too small, InnoDB checkpoints aggressively, causing write stalls. Adaptive flushing attempts to spread writes evenly over time, but can still spike under heavy load.
The doublewrite buffer protects against partial page writes — InnoDB writes a page twice before applying to the tablespace. This adds a small write overhead but prevents data corruption on crash.
For write-heavy workloads, tune innodb_log_file_size to avoid checkpoint pressure, and consider innodb_flush_log_at_trx_commit for durability vs performance trade-offs.
write_path_check.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
12
13
-- TheCodeForge — check redo log pressureSHOWSTATUSLIKE 'Innodb_log_waits'; -- >0 indicates log contention-- Check current log file sizeSELECT @@innodb_log_file_size;
-- Check checkpoint age (in bytes)SELECT variable_value - LSNAS checkpoint_age
FROM performance_schema.global_status
WHERE variable_name = 'Innodb_redo_log_current_lsn';
-- Set doublewrite buffer statusSHOWVARIABLESLIKE'innodb_doublewrite';
Output
Innodb_log_waits: 0
@@innodb_log_file_size: 268435456
checkpoint_age: 104857600
innodb_doublewrite: ON
Write Path Strategy
On SSDs, the doublewrite buffer overhead is small — keep it enabled. On NVMe, you may disable it if your filesystem guarantees atomic write size, but test carefully.
Production Insight
Too many log waits mean checkpoints can't keep up with write rate.
Monitor Innodb_log_waits and Innodb_pages_written periodically.
A common fix is doubling the log file size, but ensure crash recovery RTO is acceptable.
Key Takeaway
Redo log size dictates write throughput ceiling.
Checkpoints are normal, but frequent stalls signal undersized logs.
Balance durability with performance by adjusting innodb_flush_log_at_trx_commit.
Write path tuning decision tree
IfInnodb_log_waits > 0
→
UseIncrease innodb_log_file_size or add more log files
IfHigh checkpoint frequency but no log waits
→
UseCheck innodb_io_capacity and innodb_io_capacity_max
IfDisk is slow (HDD)
→
UseConsider innodb_flush_log_at_trx_commit=2, but review durability requirements
● Production incidentPOST-MORTEMseverity: high
Slow Dashboard Query After Data Migration
Symptom
The dashboard took 34 seconds to load — timeouts occurred during peak hours. Application logs showed the query was spending 99% of its time in 'Sending data' state.
Assumption
The team assumed the query was slow because of the large table size and tried increasing server RAM. That didn't help.
Root cause
The JOIN column customer_id had no index on the orders table. MySQL was performing a full table scan of 10 million rows for every report request. EXPLAIN showed type=ALL and rows=10M.
Fix
Added a B-tree index on orders.customer_id and on orders.order_date for the date filter. Query time dropped from 34 seconds to 220ms.
Key lesson
Always run EXPLAIN on new queries before deploying to production.
Index foreign key columns by default — they are JOIN paths.
Don't assume more RAM fixes plan problems; read the execution plan first.
Production debug guideSymptom → Action framework for MySQL performance issues6 entries
Symptom · 01
Query times out or takes >5 seconds
→
Fix
Enable slow query log: SET GLOBAL slow_query_log=ON; long_query_time=2. Capture the query and run EXPLAIN.
Symptom · 02
EXPLAIN shows type=ALL (full table scan)
→
Fix
Create an index on the column used in WHERE or JOIN. Use cardinality check with SHOW INDEXES.
Symptom · 03
High disk I/O on data directory
→
Fix
Check buffer pool hit ratio: SHOW STATUS LIKE 'Innodb_buffer_pool_read_requests' and 'Innodb_buffer_pool_reads'. If ratio < 99%, increase innodb_buffer_pool_size.
Symptom · 04
Optimizer chooses wrong index
→
Fix
Run ANALYZE TABLE to update statistics. Use FORCE INDEX or create a composite index covering the query.
Symptom · 05
High InnoDB log file writes (redo log contention)
→
Fix
Check innodb_log_file_size and innodb_log_files_in_group. If the log buffer waits signal contention, increase log size or buffer size.
Symptom · 06
Connection pool exhaustion (too many threads waiting for DB)
→
Fix
Check SHOW FULL PROCESSLIST for many 'Waiting for ... lock' or 'Sleep' threads. Tune max_connections, wait_timeout, and application pool size.
★ MySQL Performance Quick Debug Cheat SheetFive common performance symptoms and the exact commands to diagnose and fix them immediately.
Query slow, no index usage−
Immediate action
Get execution plan
Commands
EXPLAIN FORMAT=JSON <your_query>;
SHOW INDEXES FROM <table>;
Fix now
Add index: CREATE INDEX idx_name ON table(column);
High disk reads, low cache hit+
Immediate action
Check buffer pool usage
Commands
SHOW STATUS LIKE '%buffer%read%';
SELECT @@innodb_buffer_pool_size;
Fix now
Increase innodb_buffer_pool_size to 70-80% of available RAM.
Suboptimal plan despite index+
Immediate action
Refresh table statistics
Commands
ANALYZE TABLE <table>;
EXPLAIN <query>;
Fix now
Drop and recreate index if statistics still stale.
Too many open tables / table cache misses+
Immediate action
Check table cache hit rate
Commands
SHOW STATUS LIKE 'Open%';
SELECT @@table_open_cache;
Fix now
Increase table_open_cache and pre-open tables with --init-file.
Deadlock or lock wait timeout+
Immediate action
Get lock info
Commands
SHOW ENGINE INNODB STATUS\G
SELECT * FROM sys.innodb_lock_waits;
Fix now
Optimize transaction scope — minimize row locks, use quick commits.
Increase innodb_log_file_size to 256MB or 512MB, or use multiple log files.
×
Setting sort_buffer_size or join_buffer_size too high globally
Symptom
Memory consumption skyrockets, OOM killer may kill MySQL
Fix
Keep default (usually 256K) and only increase per-session for specific queries.
INTERVIEW PREP · PRACTICE MODE
Interview Questions on This Topic
Q01SENIOR
How does InnoDB use the buffer pool, and what metrics tell you it's size...
Q02SENIOR
Explain the difference between a clustered and secondary index in InnoDB...
Q03SENIOR
What does 'Using filesort' mean in an EXPLAIN output, and how do you fix...
Q04SENIOR
You deployed a query that was fast in test but slow in production after ...
Q05SENIOR
How does the InnoDB redo log affect write performance?
Q06SENIOR
What is the impact of using UUID as a primary key in InnoDB?
Q01 of 06SENIOR
How does InnoDB use the buffer pool, and what metrics tell you it's sized correctly?
ANSWER
InnoDB caches table and index pages in the buffer pool. The key metric is buffer pool hit ratio: (read requests - actual reads) / read requests * 100. A value below 99% means your working set doesn't fit. Use SHOW STATUS LIKE 'Innodb_buffer_pool_read%' to get these numbers. Proper sizing: 70-80% of RAM on a dedicated server, but not so high that the OS swaps.
Q02 of 06SENIOR
Explain the difference between a clustered and secondary index in InnoDB. How does this affect query performance?
ANSWER
InnoDB stores data in a clustered index on the primary key — the table data is itself a B+tree ordered by PK. Secondary indexes store indexed columns plus the PK value. A lookup via secondary index does two tree traversals: first to find the PK, then to the clustered index for the full row. This means secondary indexes are larger and slightly slower for single-row lookups but still fast. Covering queries (all needed columns in the index) avoid the second traversal entirely.
Q03 of 06SENIOR
What does 'Using filesort' mean in an EXPLAIN output, and how do you fix it?
ANSWER
It means MySQL sorted the result set in temporary memory (or a temp table on disk) because the ORDER BY clause couldn't use an existing index. To fix: create an index that includes the ORDER BY columns in the same order, and ensure the index columns are the same ones used in the WHERE clause's leftmost prefix. If you can't avoid filesort, increase sort_buffer_size for the session.
Q04 of 06SENIOR
You deployed a query that was fast in test but slow in production after a week. What's the first thing you check?
ANSWER
Run EXPLAIN on the query in production. Likely the execution plan changed due to data growth or stale statistics. Compare with the plan from testing. If it changed, run ANALYZE TABLE to refresh stats. If that doesn't help, check if the query is doing a full table scan (type=ALL) and add the missing index. Sometimes the optimizer chooses a different index because the table grew — consider a composite index that covers all filter conditions.
Q05 of 06SENIOR
How does the InnoDB redo log affect write performance?
ANSWER
The redo log records every logical change. InnoDB writes to the log sequentially, which is faster than random writes to the tablespace. The parameter innodb_log_file_size determines how much log space is available. A small log causes frequent checkpoints, blocking writes. A log that's too large increases crash recovery time. Monitor Innodb_log_waits — if >0, increase log size or log file count.
Q06 of 06SENIOR
What is the impact of using UUID as a primary key in InnoDB?
ANSWER
UUIDs are random and large (16 bytes vs 4 for INT). They cause index fragmentation and frequent page splits because insert order doesn't match key order. This leads to a 20-40% write throughput loss. Better to use a sequential integer or ULID (time-ordered UUID). If you must use UUID, store it in binary(16) format rather than char(36).
01
How does InnoDB use the buffer pool, and what metrics tell you it's sized correctly?
SENIOR
02
Explain the difference between a clustered and secondary index in InnoDB. How does this affect query performance?
SENIOR
03
What does 'Using filesort' mean in an EXPLAIN output, and how do you fix it?
SENIOR
04
You deployed a query that was fast in test but slow in production after a week. What's the first thing you check?
SENIOR
05
How does the InnoDB redo log affect write performance?
SENIOR
06
What is the impact of using UUID as a primary key in InnoDB?
SENIOR
FAQ · 6 QUESTIONS
Frequently Asked Questions
01
What is MySQL Performance Tuning in simple terms?
MySQL Performance Tuning is a fundamental concept in Database. Think of it as a tool — once you understand its purpose, you'll reach for it constantly.
Was this helpful?
02
Should I add an index on every column used in a WHERE clause?
Not necessarily. Indexes on low-selectivity columns (like boolean flags) rarely help. Also, each index adds write overhead. Only index columns that are selective, used in JOINs, or appear in ORDER BY. Use composite indexes for multi-column filters.
Was this helpful?
03
How often should I run ANALYZE TABLE?
After any significant data change: bulk INSERT, DELETE, or UPDATE that modifies more than 10% of rows. Also after importing a large dump. In MySQL 8.0, innodb_stats_auto_recalc is on by default, but manual ANALYZE ensures accuracy.
Was this helpful?
04
What is the buffer pool hit ratio and how do I improve it?
It's the percentage of page requests served from memory without disk I/O. Metric = (Innodb_buffer_pool_read_requests - Innodb_buffer_pool_reads) / Innodb_buffer_pool_read_requests * 100. Target >99%. To improve: increase innodb_buffer_pool_size, reduce query scan sizes, or add indexes to limit rows examined.
Was this helpful?
05
Why does my query run fast sometimes and slow other times?
Two common causes: (1) Other queries are evicting your hot pages from the buffer pool, causing disk reads. (2) The optimizer changes plan after table statistics update or data growth. Check performance_schema for wait events and run EXPLAIN multiple times to detect plan instability.
Was this helpful?
06
How do I choose the right InnoDB log file size?
Start with 256MB per log file, with 2 files (256MB * 2 = 512MB total). Monitor Innodb_log_waits. If waits are frequent, double the size. But keep recovery time in mind — a 1GB log can take minutes to recover after a crash. Balance write throughput with RTO.