DevNotes
Support
SQL & MySQL · Chapter 30

Performance Tuning & Interview Masterclass

Query optimization, EXPLAIN and EXPLAIN ANALYZE, the slow query log, join and pagination strategies, partitioning, common production problems, the top 20 interview questions and a revision roadmap.

◷ 16 min read§ 16 sections? 20 interview questions{ } 23 code examples

#Introduction

This chapter brings together everything from the earlier chapters and shows how MySQL is optimized in real enterprise applications.

It covers query optimization, an EXPLAIN deep dive, the slow query log, join optimization, pagination strategies, partitioning, common production problems, the top interview questions, production best practices and a complete revision roadmap — the knowledge expected from an experienced Java backend developer.

#Topic Review

  • SQL & MySQL
    • FundamentalsSELECT, WHERE, ORDER BY, LIMIT, DISTINCT, CRUD
    • JoinsINNER, LEFT, RIGHT, SELF, CROSS
    • ReportingGROUP BY, HAVING, aggregate functions
    • Advanced SQLsubqueries, correlated subqueries, CTEs, window functions
    • Design & performancekeys, constraints, normalization, indexes, views, stored procedures

#Query Optimization

Avoid SELECT *

SQL
-- Poor: on a 50-million-row table this is extremely expensive
SELECT * FROM employee;

-- Better: retrieve only the required columns
SELECT employee_name, salary FROM employee;

SELECT * causes:

  • more disk I/O;
  • more memory usage;
  • more network traffic and unnecessary data transfer;
  • lost covering-index opportunities;
  • application code that depends on the table structure.

Enterprise rule: avoid SELECT * in production queries unless every column is genuinely required.

Use WHERE Properly

Filter unnecessary rows as early as possible:

SQL
SELECT employee_id, employee_name, salary
FROM employee
WHERE department = 'IT';

Optimize JOINs

SQL
SELECT o.order_id, o.order_date, c.customer_name
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id;

Columns used in join conditions should be indexed:

SQL
CREATE INDEX idx_orders_customer_id ON orders (customer_id);

customers.customer_id is usually already indexed as the primary key. (InnoDB also creates an index on a foreign-key column automatically if none exists.)

Use Composite Indexes

SQL
SELECT order_id, order_date, status
FROM orders
WHERE customer_id = 100
  AND order_date > '2026-01-01';

CREATE INDEX idx_customer_order_date ON orders (customer_id, order_date);

Why this order? customer_id is an equality condition and order_date is a range condition. Equality columns go first, the range column last, so the index matches the query's access pattern.

Avoid Functions on Indexed Columns

SQL
-- Bad: the function prevents efficient use of an index on order_date
SELECT * FROM orders WHERE YEAR(order_date) = 2026;

-- Better
SELECT *
FROM orders
WHERE order_date >= '2026-01-01'
  AND order_date <  '2027-01-01';

This is also safer than BETWEEN when the column holds date and time values, because the upper boundary is explicit and exclusive. A predicate the optimizer can use an index for is called sargable.

Let the Database Count

SQL
-- Bad: fetch every row, then count in Java
SELECT * FROM orders;

-- Better: return only the count
SELECT COUNT(*) FROM orders;

EXISTS vs IN

EXISTS expresses "do matching rows exist?" clearly:

SQL
SELECT c.customer_id, c.customer_name
FROM customer c
WHERE EXISTS (
    SELECT 1
    FROM orders o
    WHERE o.customer_id = c.customer_id
);

IN can be just as efficient in modern MySQL, because the optimizer may transform both into a semi-join. Don't assume EXISTS is always faster than IN — choose by correct semantics (remember the NOT IN + NULL trap), index properly and verify with EXPLAIN / EXPLAIN ANALYZE.

#EXPLAIN Deep Dive

SQL
EXPLAIN
SELECT * FROM employee WHERE department = 'IT';
idtypekeyrowsExtra
1refidx_department200Using where
ColumnMeaning
typeAccess method
possible_keysIndexes MySQL could use
keyIndex actually selected
key_lenHow much of the index is used (useful for composite indexes)
refColumns or constants compared with the index
rowsEstimated rows examined
filteredEstimated percentage of rows passing the condition
ExtraAdditional execution information

Important type Values

TypeMeaning
systemTable has only one row
constAt most one matching row
eq_refOne matching row for each row of the previous table
refNon-unique index lookup
rangeIndex range scan
indexFull index scan
ALLFull table scan

For large tables, investigate an unexpected ALL. A full table scan isn't automatically wrong, but it's expensive when only a few rows are needed.

Important Extra Values

ExtraMeaning
Using indexCovering index — no table lookup
Using whereRows are filtered after being read
Using filesortAn extra sort step (the index doesn't provide the order)
Using temporaryA temporary table is used (common with GROUP BY / DISTINCT)

EXPLAIN ANALYZE

MySQL 8.0.18+ supports:

SQL
EXPLAIN ANALYZE
SELECT * FROM employee WHERE salary > 70000;

Unlike EXPLAIN, EXPLAIN ANALYZE executes the query and reports actual timings and row counts, so you can compare optimizer estimates with actual execution. It helps find incorrect row estimates, expensive operations, slow joins, unexpected scans and bottlenecks.

#Slow Query Log

MySQL can record slow-running queries:

Text
slow_query_log  = ON
long_query_time = 2      # seconds

Queries slower than the threshold are logged for analysis. Production teams use the slow query log to find frequently slow queries, missing indexes, expensive scans, poor join strategies and queries needing optimization. Tools such as mysqldumpslow or Percona's pt-query-digest aggregate the log by query pattern; log_queries_not_using_indexes can also log unindexed queries.

#Pagination Strategies

LIMIT / OFFSET Pagination

SQL
SELECT *
FROM employee
ORDER BY employee_id
LIMIT 10 OFFSET 10000;

The database still has to read and skip the first 10,000 rows before returning the next 10. As the offset grows:

OffsetCost
100Usually manageable
100,000More expensive
10,000,000Potentially very expensive

Keyset Pagination (Seek Method)

Instead of a large offset, continue from the last key seen:

SQL
SELECT *
FROM employee
WHERE employee_id > 10000
ORDER BY employee_id
LIMIT 10;
PageConditionRemembered last ID
1(none)10
2WHERE employee_id > 1020
3WHERE employee_id > 2030

Benefits: efficient for large datasets, no scanning and skipping of large offsets, works well for infinite scrolling, and stable when rows are inserted between requests.

OFFSET vs Keyset

OFFSET paginationKeyset pagination
Easy to implementRequires a cursor / last key
Can jump directly to page numbersBest for next/previous navigation
Slower at large offsetsEfficient for large datasets
Can skip or repeat rows when data changesMore stable with deterministic ordering

#Partitioning

A table with 500 million rows may be partitioned by year, month, region or numeric ranges:

SQL
CREATE TABLE orders (
    order_id    BIGINT NOT NULL,
    order_date  DATE   NOT NULL,
    customer_id BIGINT NOT NULL,
    amount      DECIMAL(12, 2),
    PRIMARY KEY (order_id, order_date)
)
PARTITION BY RANGE (YEAR(order_date)) (
    PARTITION p2024 VALUES LESS THAN (2025),
    PARTITION p2025 VALUES LESS THAN (2026),
    PARTITION p2026 VALUES LESS THAN (2027),
    PARTITION pmax  VALUES LESS THAN MAXVALUE
);

Benefits can include partition pruning, easier archival, easier management of very large datasets and fast deletion of old data (ALTER TABLE orders DROP PARTITION p2024 instead of a huge DELETE).

Partition Pruning

With the table partitioned by year:

SQL
SELECT *
FROM orders
WHERE order_date >= '2026-01-01'
  AND order_date <  '2027-01-01';

MySQL can scan only the relevant partition instead of all of them — partition pruning. EXPLAIN shows the partitions touched in its partitions column.

#Denormalization

A highly normalized schema means many JOINs, which can make reporting queries expensive. Enterprise reporting systems may use summary tables, denormalized schemas, precomputed aggregates, materialized-view patterns and data warehouses to improve read performance.

Denormalization should be intentional, measured and documented — don't denormalize without a demonstrated performance requirement.

#Common Production Problems

1. Missing Index

SQL
SELECT * FROM customer WHERE email = 'abc@gmail.com';

No suitable index → large table → full scan → slow query. A possible fix:

SQL
CREATE UNIQUE INDEX idx_customer_email ON customer (email);

2. The N+1 Query Problem

Load 100 customers — 1 query
For each customer, load orders — 100 queries
101 queries in total

Common with ORM frameworks such as Hibernate. Solutions: proper JOINs, fetch joins (JOIN FETCH), entity graphs, batch fetching (@BatchSize), DTO projections and query-specific fetching strategies.

3. Cartesian Join

SQL
SELECT * FROM a, b;   -- no join condition

With 10,000 rows in each table, the result has 100,000,000 rows. Always verify join conditions.

4. Locking Issues

BEGIN
Update rows
Application does slow work
More queries
COMMIT

Locks are held for the whole time. Keep transactions short, focused and consistent, and commit or roll back quickly — don't call external services inside a transaction.

5. Too Many Indexes

Indexes help reads but add overhead to every INSERT, UPDATE and DELETE. Monitor and remove genuinely unused or redundant indexes after careful analysis (sys.schema_unused_indexes and sys.schema_redundant_indexes help).

6. Large Result Sets

Returning millions of rows to a Java application causes high database load, high network usage, high application memory and long response times. Use pagination, streaming where appropriate, filtering, aggregation and batch processing.

#Top 20 Interview Questions

Q11. Explain the logical SQL query processing order.
FROM / JOIN
WHERE
GROUP BY
HAVING
Window functions
SELECT
DISTINCT
ORDER BY
LIMIT

This is the logical order, not necessarily the physical order the optimizer chooses. Window functions are evaluated after HAVING, which is why they can't be used in WHERE.

Q22. Difference between WHERE and HAVING?

WHERE filters rows before grouping and can't filter aggregate results directly. HAVING filters groups after aggregation and is used with aggregate functions.

Q33. Clustered vs secondary index?

In InnoDB the primary key is the clustered index — its leaf nodes contain the row data. A secondary index stores the secondary key plus the primary-key value, which is then used for a clustered-index lookup.

Q44. What is a covering index?

An index containing every column needed to satisfy a query. For SELECT department, salary FROM employee WHERE department = 'IT', the index (department, salary) lets the query be answered directly from the index.

Q55. Explain the leftmost-prefix rule.

An index on (a, b, c) efficiently supports the leading prefixes (a), (a, b) and (a, b, c). Skipping the leading column usually prevents normal use of the composite index for lookups.

Q66. Difference between ROW_NUMBER(), RANK() and DENSE_RANK()?
SalaryROW_NUMBER()RANK()DENSE_RANK()
100111
100211
90332

ROW_NUMBER() always gives unique numbers; RANK() gives ties the same rank and leaves gaps; DENSE_RANK() gives ties the same rank without gaps.

Q77. CTE vs subquery?

CTEs improve readability, break complex logic into named steps, can be referenced several times, and support recursion for hierarchical queries. Subqueries suit smaller nested operations and are concise for simple cases. Performance depends on the query and optimizer.

Q88. Procedure vs function?

A stored procedure is called with CALL, can perform multi-step operations and supports IN, OUT and INOUT. A stored function returns one value, can be used inside SQL expressions and is typically used for reusable calculations.

Q99. Explain normalization.

Organizing data to reduce unnecessary redundancy, prevent update anomalies and improve integrity. Common forms: 1NF → 2NF → 3NF → BCNF.

Q1010. Explain EXPLAIN.

EXPLAIN shows the optimizer's execution plan — access methods, index selection, join order, estimated rows and additional operations.

Q1111. Why is SELECT * discouraged?

It retrieves unnecessary columns and increases I/O, network traffic and memory use, and it can prevent covering-index optimizations.

Q1212. DELETE vs TRUNCATE vs DROP?
DELETETRUNCATEDROP
RemovesSelected rows (WHERE)All rowsThe whole table
TypeDMLDDLDDL
RollbackYes, inside a transactionNo — implicit commitNo — implicit commit
Fires DELETE triggersYesNoNo
Resets AUTO_INCREMENTNoYesTable is gone
SQL
DELETE FROM employee WHERE employee_id = 101;
TRUNCATE TABLE employee;
DROP TABLE employee;
Q1313. Explain the ACID properties.
  • Atomicity — all operations succeed or all fail.
  • Consistency — transactions move the database from one valid state to another.
  • Isolation — concurrent transactions don't incorrectly interfere with each other.
  • Durability — committed data survives failures (InnoDB's redo log).
Q1414. What is a deadlock?

Transactions waiting for each other's locks in a cycle:

Transaction ATransaction B
Locks row 1Locks row 2
Waits for row 2Waits for row 1

InnoDB detects the deadlock and rolls back one transaction (the "victim", error 1213). Applications should be prepared to retry deadlock victims, and can reduce deadlocks by accessing rows in a consistent order.

Q1515. What is MVCC?

Multi-Version Concurrency Control keeps multiple versions of rows so consistent reads can usually happen without blocking writers. InnoDB implements it with undo logs, transaction IDs and read views.

Q1616. Explain isolation levels.

MySQL supports READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ and SERIALIZABLE. InnoDB's default is REPEATABLE READ. Higher isolation gives stronger consistency guarantees but may increase contention.

Q1717. What is an index scan?

Reading entries from an index. Depending on the query, MySQL may do an index lookup, a range scan, a full index scan or a full table scan. An index scan isn't automatically efficient — the number of rows examined still matters.

Q1818. What is keyset pagination?

Paging with the last retrieved ordering key instead of a large OFFSET:

SQL
SELECT *
FROM employee
WHERE employee_id > 10000
ORDER BY employee_id
LIMIT 10;
Q1919. When would you denormalize?

When measured read-performance requirements justify controlled redundancy — reporting, analytics, dashboards, data warehouses and precomputed summaries.

Q2020. How do you troubleshoot a slow query?
  1. Identify the slow query (slow query log, APM).
  2. Run EXPLAIN.
  3. Use EXPLAIN ANALYZE where appropriate.
  4. Check indexes.
  5. Check rows examined.
  6. Review join order and conditions.
  7. Remove unnecessary columns.
  8. Rewrite non-sargable predicates.
  9. Check locking and transaction behavior.
  10. Measure again after optimizing.

Never assume an optimization worked — verify it.

#Enterprise Performance Checklist

Before deploying important SQL:

  • Avoid unnecessary SELECT *.
  • Index according to actual query patterns.
  • Verify execution plans with EXPLAIN, and EXPLAIN ANALYZE when actual metrics are needed.
  • Reduce unnecessary rows and columns.
  • Optimize join conditions; use covering indexes where beneficial.
  • Prefer keyset pagination for large sequential datasets.
  • Keep transactions short.
  • Monitor slow queries, and review unused and redundant indexes.
  • Measure before and after optimizing.

#Mini Project — Optimize a Query

SQL
SELECT *
FROM orders o
JOIN customer c ON o.customer_id = c.customer_id
WHERE YEAR(o.order_date) = 2026
ORDER BY o.order_date DESC;

Step 1 — remove SELECT *:

SQL
SELECT o.order_id, o.order_date, o.amount, c.customer_id, c.customer_name
FROM orders o
JOIN customer c ON o.customer_id = c.customer_id
WHERE YEAR(o.order_date) = 2026
ORDER BY o.order_date DESC;

Step 2 — rewrite the date filter:

SQL
SELECT o.order_id, o.order_date, o.amount, c.customer_id, c.customer_name
FROM orders o
JOIN customer c ON o.customer_id = c.customer_id
WHERE o.order_date >= '2026-01-01'
  AND o.order_date <  '2027-01-01'
ORDER BY o.order_date DESC;

Step 3 — add appropriate indexes:

SQL
CREATE INDEX idx_orders_order_date_customer ON orders (order_date, customer_id);

The index serves the date range and the ORDER BY (MySQL can read it backwards for DESC, avoiding a filesort), and includes customer_id for the join. Make sure customer.customer_id is indexed — normally through its primary key.

Step 4 — run EXPLAIN on the rewritten query and check type, possible_keys, key, rows, filtered and Extra.

Step 5 — explain the improvements:

BeforeAfter
SELECT *Required columns only
YEAR(order_date) — poor index useIndex-friendly date range
Unindexed access pathIndexed range and join columns
UnverifiedExecution plan verified

Result: less work and better scalability.

#Final Practice Challenges

Easy

  1. Explain GROUP BY.
  2. Explain HAVING.
  3. Explain LEFT JOIN.
  4. Explain a primary key.
  5. Explain a foreign key.

Medium

  1. Write a query using a CTE.
  2. Rank employees by salary.
  3. Find the second-highest salary.
  4. Build a running-total report.
  5. Optimize a slow query.

Advanced

  1. Design an e-commerce database.
  2. Optimize a complex JOIN query.
  3. Explain ACID.
  4. Design indexes for a banking application.
  5. Build a complete executive dashboard using window functions and CTEs.

#Best Practices

  • Understand execution plans before optimizing.
  • Write correct, readable SQL first, then optimize based on evidence.
  • Prefer window functions over overly complex self-joins where appropriate.
  • Design indexes from real query patterns, not assumptions, and understand the leftmost-prefix rule.
  • Balance normalization with reporting performance.
  • Use transactions correctly; understand locking, deadlocks and isolation levels.
  • Recognize and solve the N+1 query problem.
  • Keep business logic primarily in the application layer unless database-side execution offers a clear benefit.

#Revision Roadmap

WeekRevisePractice
1 — SQL fundamentalsSELECT, WHERE, operators, CRUD, ORDER BY, LIMIT, string and date functions20–30 SQL queries
2 — Joins & aggregationINNER, LEFT, RIGHT and SELF JOIN, GROUP BY, HAVING, aggregate functions15–20 join and reporting problems
3 — Advanced SQLSubqueries, correlated subqueries, CTEs, window and ranking functions, LEAD(), LAG()15–20 advanced SQL problems
4 — Design & performanceKeys, constraints, normalization, indexes, views, stored procedures, transactions, EXPLAIN, query optimizationAnalyze and optimize real SQL queries

Where to Go Next

AreaTopics
Database depthReplication, backup and recovery, advanced partitioning, InnoDB architecture, optimizer internals, locking and deadlock analysis
Spring DataSpring Data JPA, Hibernate performance tuning, JPQL, Criteria API, entity graphs, batch processing, N+1 optimization
System designDatabase scaling, sharding, read replicas, Redis caching, message queues, microservice data patterns, distributed transactions
Production engineeringDocker, Kubernetes, CI/CD, monitoring, observability, database metrics, production troubleshooting

#Key Takeaways

  • Fetch only the columns and rows you need; let the database filter, aggregate and count.
  • Index join columns and design composite indexes as equality columns first, range column last.
  • Keep predicates sargable — no functions on indexed columns; use half-open date ranges.
  • Read EXPLAIN (type, key, rows, Extra) and confirm with EXPLAIN ANALYZE.
  • Use the slow query log to find what to optimize.
  • Prefer keyset pagination for deep pages; partition only very large tables, and never instead of indexing.
  • Watch for missing indexes, N+1 queries, Cartesian joins, long transactions, too many indexes and huge result sets.
  • Measure before and after every optimization.