"Simple SQL" is a luxury that usually only exists in documentation and tutorials. In production, schemas grow complex, datasets get massive, and queries that worked in staging can suddenly crawl to a halt.
Here are five lessons I’ve learned about SQL performance while building and scaling applications.
1) The Hidden Cost of Over-Indexing
Indexes are essential for speed, acting as a lookup table (often a B-Tree structure) so the database doesn't have to perform a full table scan. However, they aren't free performance.
The trade-off: Every index adds overhead to INSERT, UPDATE, and DELETE operations because the database must update index structures alongside table data.
The lesson: Focus indexes on columns frequently used in WHERE clauses, JOIN conditions, and ORDER BY statements. Monitor for unused indexes—they are often dead weight slowing down writes.
2) Query Structure Is Logic, Not Just Syntax
How you write a query determines the execution plan the database engine creates. A common mistake is assuming the engine will always find the most efficient path.
- Application-side processing: Sometimes, it’s faster to run two focused queries and merge results in application code than forcing the database to execute a massive nested query.
- Selectivity matters: Prefer
SELECT <columns>overSELECT *. Smaller payloads reduce I/O and memory pressure during sorting and aggregation.
3) Order of Operations in JOINs and WHEREs
While modern optimizers are smart, your filter structure still matters at scale.
- Filter early: Reduce result sets as soon as possible so fewer rows enter expensive joins.
- Write SARGable predicates: Avoid wrapping indexed columns in functions.
Bad (often disables index usage):
WHERE YEAR(created_at) = 2024
Better:
WHERE created_at >= '2024-01-01'
AND created_at < '2025-01-01'
4) The Danger of Correlated Subqueries
A correlated subquery depends on values from the outer query. In many cases, this can become effectively (O(n^2)), because the inner query runs once for each outer row.
The fix: Rewrite as JOINs or Common Table Expressions (CTEs) when possible. CTEs also improve readability and can make optimization opportunities clearer to both humans and the planner.
5) Schema Design: The Normalization Balance
Database design is always a tension between integrity and speed.
- Normalization: Great for reducing redundancy and preserving data quality, but can lead to “join hell” on read-heavy paths.
- Denormalization: In high-read environments, strategically duplicating data (for example, storing
usernamedirectly onposts) can dramatically reduce join costs.
There’s no universal right answer—only trade-offs that should match your access patterns.
Final Thoughts
Optimization is rarely about a single silver bullet. It’s about understanding real query behavior under real workload.
Use EXPLAIN ANALYZE (or your database equivalent) to move from guessing to knowing.
What are your own rules of thumb when the database starts slowing down? Let’s discuss in the comments.