Database Indexing Strategies: Optimizing Laravel Query Performance
Master database indexing strategies to boost performance. Learn to analyze execution plans, implement composite indexes, and optimize complex search queries.
Previously in this course, we explored Eloquent Performance Optimization by addressing N+1 issues and eager loading. While eager loading fixes how we retrieve related data, it doesn't solve the underlying speed of the queries themselves. In this lesson, we move to the database engine layer to ensure our queries execute in milliseconds, even as our project board grows to millions of rows.
Understanding Query Execution Plans
Before you add an index, you must know what the database is actually doing. Database performance is often hindered by "Full Table Scans," where the engine reads every single row in a table to find a match.
To see the truth, use the EXPLAIN statement in your SQL client or via Laravel's DB facade. If you are using MySQL or PostgreSQL, you can prefix any query with EXPLAIN to see the "plan."
PHP$results = DB::select("EXPLAIN SELECT * FROM tasks WHERE status = 'completed' AND user_id = 5"); dump($results);
Look for the type and key columns in the output:
- type: ALL: This is the red flag. It means a full table scan.
- key: NULL: No index is being used.
- type: ref/range: These are good. The database is using an index to find rows.
The Power of Composite Indexes
A common mistake is creating individual indexes on every column. If you frequently query tasks by user_id and status, a single index on user_id is often insufficient. Instead, use a composite index (also known as a multi-column index).
The order of columns in a composite index matters immensely due to the B-Tree structure. The database engine can use the index if the query uses the leftmost prefix of the index.
For our project board, if we often filter by user_id then status, we should define our migration like this:
PHPSchema::table('tasks', function (Blueprint $table) { #6A9955">// The order matters: user_id is the primary filter $table->index(['user_id', 'status', 'created_at']); });
With this index, a query filtering by user_id AND status will be lightning fast. However, a query filtering only by status will likely ignore this index because status is not the first column.
Optimizing Search Queries
When building search functionality, simple LIKE queries can be slow. If you need to search across columns, ensure your index supports the search pattern.
For example, if you are searching tasks by title:
SQLSELECT * FROM tasks WHERE title LIKE 'Fix bug%'; -- FAST: Uses index SELECT * FROM tasks WHERE title LIKE '%bug%'; -- SLOW: Index ignored
Leading wildcards (%text) force the database to scan the entire index, which is why full-text search engines like Meilisearch or PostgreSQL's GIN indexes are often preferred for complex searches. We will cover those in Building a Search API.
Hands-on Exercise
- Open your project board migration for the
taskstable. - Identify the three most frequent queries your application runs (e.g., filtering by project, status, and due date).
- Create a new migration to add a composite index covering these columns.
- Run
php artisan migrate. - Use
EXPLAINon one of your filtered queries to verify thekeycolumn now shows your new index name instead ofNULL.
Common Pitfalls
- Over-indexing: Every index adds overhead to
INSERT,UPDATE, andDELETEoperations because the database must update the index tree every time data changes. Only index what you actually query. - Ignoring Cardinality: Indexing a column with very low cardinality (like
is_activewhere 99% of rows are1) is often useless. The database optimizer will likely ignore the index because it's faster to just scan the table. - Functions on Indexed Columns: Using
WHERE YEAR(created_at) = 2023prevents the database from using an index oncreated_at. UseWHERE created_at BETWEEN '2023-01-01' AND '2023-12-31'instead.
Recap
Database indexing is the most effective way to scale query performance. By analyzing execution plans, you avoid guesswork. By implementing composite indexes, you satisfy complex filtering needs. Remember: index for your most common read patterns, but don't over-index and degrade your write performance.
For deeper dives into advanced techniques, refer to our previous discussions on Laravel Database Performance: Mastering B-Tree Indexing Strategies and Partial Indexing Strategies to Boost Database Performance and Storage Efficiency.
Up next: We will discuss Using Value Objects to clean up our domain logic and encapsulate complex attribute rules.
Work with me

FilamentPHP Admin Panel & Dashboard Development
A powerful admin panel for your Laravel app โ built with FilamentPHP so you can manage everything without touching the database.

Laravel REST API Development
Clean, secure, well-documented Laravel REST APIs โ the backend engine for your app, mobile client, or SaaS. Built by an API specialist.