Summary: ORMs make development faster, but mastering SQL and database internals turns good backend engineers into great ones. Here’s why understanding both levels — abstraction and reality — matters.
⚙️ Context & Problem
Modern web frameworks — Django, FastAPI, Spring Boot, Laravel — all encourage the use of Object-Relational Mappers (ORMs). They make it easy to map objects to tables, run queries in a few lines of code, and forget about SQL altogether.
But that convenience comes with a price.
Over time, many backend developers stop thinking in terms of relations, indexes, and execution plans. They rely on abstractions and lose visibility into what’s actually happening inside the database engine.
The result? Applications that work but don’t scale. Queries that are easy to write but painful to debug. APIs that return data but take seconds longer than they should.
🎯 Objective
To show why even in the ORM era, backend developers must master SQL, database design, and performance tuning — not just for optimization, but for understanding how data truly moves through their system.
🧱 Architectural Reality
An ORM is a translation layer. It converts your object-oriented code into SQL queries. This abstraction speeds up development, but it also hides:
- The actual queries being executed
- The number of database round-trips per request
- How indexes are (or aren’t) being used
- When locks, transactions, or deadlocks happen
Most backend performance issues are not caused by Python or Java code — but by inefficient queries and poor schema design.
You can’t optimize what you don’t understand. And you can’t understand a database by reading only ORM models.
🧠 The Two Sides of Backend Development
1. The ORM Perspective
ORMs like SQLAlchemy, Django ORM, and Hibernate bring developer productivity and maintainability.
✅ Advantages:
- Faster CRUD operations and migrations
- Database independence (PostgreSQL, MySQL, SQLite…)
- Built-in relationship management
- Easier integration with business logic
⚠️ Limitations:
- Hidden queries (N+1 problem, redundant joins)
- Slow queries when filtering large datasets
- Difficulty in debugging or optimizing performance
- Limited control over advanced SQL features (CTEs, indexes, window functions)
Example:
# ORM query
users = session.query(User).filter(User.age > 30).all()
Simple, elegant, readable.
But what’s the generated SQL?
Is it using an index on age?
How many rows are scanned?
You only know that if you inspect the query.
2. The SQL Perspective
When you write or inspect SQL directly, you get control and visibility.
✅ Advantages:
- Full transparency of what runs against the DB
- Direct access to query optimizations (
EXPLAIN ANALYZE) - Ability to use CTEs, subqueries, and aggregations efficiently
- Better understanding of locking, isolation levels, and transactions
Example:
EXPLAIN ANALYZE
SELECT * FROM users WHERE age > 30;
You’ll immediately see:
- If it’s doing a sequential scan or using an index
- The query cost
- The number of rows processed
This is real backend engineering — optimizing from the ground up.
🔍 Real-World Scenarios
🔸 Scenario 1: The Hidden N+1 Problem
# ORM code
for user in session.query(User).all():
print(user.profile.name)
This innocent loop executes one query per user — easily hundreds of queries per request.
With proper SQL understanding, you’d recognize the need for eager loading (join or selectinload) to fetch all profiles in one query.
🔸 Scenario 2: Missing Indexes
ORMs don’t automatically create performance indexes.
If you filter on status_id or created_at frequently, but never index them, performance collapses as data grows.
A single line of DDL can make a 10× difference:
CREATE INDEX idx_status_id ON orders (status_id);
🔸 Scenario 3: Data Integrity and Locking
When several API requests modify the same data concurrently, you need to understand:
- Transaction isolation (READ COMMITTED, REPEATABLE READ…)
- Row-level locks (
FOR UPDATE) - Deadlock prevention
ORMs won’t protect you from race conditions if you don’t know the transaction model underneath.
📊 Results & Impact
Backend developers who truly understand SQL and performance fundamentals gain:
- 10× faster debugging of performance issues
- Higher-quality ORM models, since they think in relational terms
- Better collaboration with DBAs and data engineers
- More stable production systems with predictable query behavior
They move from trial and error to causal reasoning. They design tables and indexes to support actual use cases, not just ORM defaults.
🛠️ Practical Roadmap to Improve SQL Mastery
-
Study Execution Plans
- Use
EXPLAINandEXPLAIN ANALYZEfor every critical query. - Understand cost estimates, indexes, and scan types.
- Use
-
Track ORM-Generated Queries
- In SQLAlchemy:
engine.echo = True - In Django: use
django.db.connection.queries
- In SQLAlchemy:
-
Learn Advanced SQL Features
- Common Table Expressions (CTEs)
- Window Functions (
ROW_NUMBER,RANK, etc.) - Indexing strategies (
btree,hash,gin,gist)
-
Practice Optimization
- Join SQL challenges (DataLemur, StrataScratch, LeetCode).
- Use real datasets to simulate heavy loads.
-
Think in Relations
- Always design models by understanding cardinality and access patterns.
- Normalize first, denormalize only when needed.
🔒 Privacy & Security
Never run raw SQL without sanitizing user input. Use parameterized queries to prevent SQL injection. Understand your ORM’s escaping and transaction management system.
💬 Conclusion & Call to Action
The best backend developers don’t just use frameworks — they understand the systems those frameworks sit on.
An ORM is a power tool, but without SQL mastery, it’s a black box. Know both. Use both. That’s how you move from “developer” to “engineer.”
Working with FastAPI, SQLAlchemy, or PostgreSQL and want to explore backend optimization? Connect with me:
- LinkedIn: Frederico Gago
- GitHub: fredericogago