Auditing Schema Performance: How to Read SQL EXPLAIN Plans
Learn how to use EXPLAIN plans to audit your schema performance. Discover how to identify slow queries in your SaaS project and optimize them for production.

Previously in this course, we explored Designing Composite Indexes: A Guide to SQL Performance and discussed the overhead of indexes. While indexes are a powerful tool, you can't optimize what you haven't measured. This lesson moves from theory to practice, teaching you how to use the EXPLAIN command to see exactly how your database engine executes your SQL queries.
Why Every Developer Needs to Audit Schema Performance
In a production SaaS environment, "it works on my machine" is a dangerous metric. A query that runs in 10ms with 50 rows of test data might take 10 seconds with 50,000 rows. Performance auditing isn't about guessing; it's about observing the query planner's choices.
When you run a SELECT statement, the database doesn't just "find the data." It builds a tree of operations—a plan—to retrieve that data in the most efficient way it knows how. The EXPLAIN command asks the database to reveal this internal map.
Decoding the EXPLAIN Plan
To audit your queries, prepend EXPLAIN (or EXPLAIN ANALYZE for actual execution statistics) to any SELECT query.
SQLEXPLAIN ANALYZE SELECT * FROM subscriptions WHERE account_id = 42;
When you run this, you'll see a series of nodes. Here is a simple mental model of what you are looking for:
- Sequential Scan (or Table Scan): The database is reading every single row in the table to find your match. This is the "red flag" of performance auditing. If your table has 100,000 rows, a sequential scan is incredibly slow.
- Index Scan: The database is using a B-tree index (like we discussed in Primary Key Indexing: How Clustered Indexes Boost Performance) to jump directly to the target rows. This is usually what you want.
- Cost/Time: Most engines provide an estimated "cost" or actual execution time. Use this to compare different versions of a query.
Worked Example: Identifying a Bottleneck
Imagine our SaaS project has a user_activity table. We want to find all logins for a specific user within a date range.
The Slow Query:
SQLEXPLAIN ANALYZE SELECT * FROM user_activity WHERE user_id = 101 ORDER BY activity_date DESC;
The Output (Simplified):
TEXTSort (cost=100.50..100.51 rows=1 width=32) (actual time=45.2ms) -> Seq Scan on user_activity (cost=0.00..100.49 rows=1 width=32) Filter: (user_id = 101)
The Audit Findings:
- Seq Scan: We are scanning the entire table.
- Cost: It took 45.2ms to find one user's data.
- The Fix: We clearly lack an index on
user_id. After addingCREATE INDEX idx_user_activity_user_id ON user_activity(user_id);, the plan changes to anIndex Scan, likely dropping the time to <1ms.
Hands-on Exercise
- Pick one of the
SELECTqueries from your project's CRUD implementation. - Run
EXPLAIN ANALYZEon that query. - Look for any line saying "Sequential Scan" on a table that you expect to be queried by an ID or a filter.
- If you find one, create an index on the column(s) used in the
WHEREclause and run theEXPLAINagain to observe the difference.
Common Pitfalls
- Ignoring "Estimated" vs. "Actual":
EXPLAINgives estimates.EXPLAIN ANALYZEactually runs the query and gives you real timing data. Always useANALYZEwhen debugging performance, but be careful withUPDATEorDELETEqueries—wrap them in a transaction (BEGIN; ... ROLLBACK;) if you don't want to change your data. - Over-Indexing: Just because a query is slow doesn't mean you should add an index for every column. Indexes slow down
INSERTandUPDATEoperations. Always verify if the query truly needs that index. - Misinterpreting "Cost": Database costs are arbitrary units. Don't worry about the specific number, worry about the change in the number when you add an index or rewrite a query.
Frequently Asked Questions
Q: Does EXPLAIN slow down my database?
A: EXPLAIN (without ANALYZE) is instantaneous. EXPLAIN ANALYZE executes the query, so it takes as long as the query itself.
Q: My query is still slow even with an index. What now?
A: You might be selecting too many columns (SELECT *). Try selecting only the columns you need. Alternatively, your index might not be "covering" the query, or your data statistics might be stale.
Q: Is a Sequential Scan ever okay? A: Yes. If the table is very small (e.g., a "settings" table with 5 rows), the database engine will intentionally perform a sequential scan because it's faster than traversing an index.
Recap
Performance auditing is the act of looking at the database's execution plan to verify that it is using your indexes as intended. By reading EXPLAIN plans, you can catch "Sequential Scans" before they become production outages.
Up next: We will look at how to manage Handling Large Data Sets to ensure your SaaS schema remains performant even as your user base grows.
Work with me

Laravel Bug Fixes, Maintenance & Optimization
Stuck on a Laravel bug or a slow app? Fast, reliable fixes, upgrades, and performance tuning from an experienced Laravel engineer.

Laravel SaaS MVP & Multi-Tenant App Development
Launch your SaaS MVP on Laravel — multi-tenant, subscription-ready, and built by the engineer behind a platform serving 10,000+ paying users.


