Laravel Database Performance: Fixing N+1 Queries and Indexing for Sub-100ms Responses
A practical Laravel database optimization playbook: identify and fix N+1 queries, design the right MySQL indexes, and reduce API latency from 843ms to 67ms and 1240ms to 89ms.
If your Laravel app feels slow under real traffic, the database is usually the bottleneck.
In this guide, I’ll walk through a repeatable laravel database optimization workflow that helped us cut two production endpoints from:
- 843ms → 67ms
- 1240ms → 89ms
The two biggest wins came from:
- Fixing N+1 query laravel patterns in Eloquent
- Adding the right mysql indexing laravel strategy for common query paths
If you’ve already tuned app-level performance with tools like Laravel Octane optimizations, this is the next layer that matters.
1) Start with measurement, not guesses
Before changing code, profile your worst endpoints.
Quick checklist
- Log SQL queries and execution time in local/staging
- Capture p95/p99 endpoint latency in production
- Find high-frequency and high-cost query paths
- Verify query plans with
EXPLAIN
Useful Laravel setup
// AppServiceProvider.php
use Illuminate\Database\Events\QueryExecuted;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
public function boot(): void
{
DB::listen(function (QueryExecuted $query) {
if ($query->time > 50) {
Log::warning('Slow query detected', [
'sql' => $query->sql,
'bindings' => $query->bindings,
'time_ms' => $query->time,
]);
}
});
}
This gives you immediate visibility into slow paths before you start tuning.
2) Eliminate N+1 queries first
In many Laravel projects, N+1 is the fastest path to bad performance.
Example problem
$posts = Post::latest()->take(50)->get();
foreach ($posts as $post) {
echo $post->author->name; // Triggers one query per post
}
You’ll see:
- 1 query for posts
- 50 queries for authors
That’s a classic N+1 query laravel issue.
Fix with eager loading
$posts = Post::with('author')
->latest()
->take(50)
->get();
Now the same endpoint typically runs in 2 queries total.
Nested relationships
$orders = Order::with([
'customer',
'items.product',
'payments'
])->latest()->paginate(20);
Also consider:
withCount()instead of loading full relationship collectionsselect()to avoid over-fetching columnsloadMissing()for conditional eager-loading
$projects = Project::query()
->select(['id', 'name', 'owner_id'])
->withCount('tasks')
->with(['owner:id,name'])
->get();
3) Design indexes based on query patterns
Indexes should reflect real WHERE + ORDER BY patterns—not assumptions.
Common anti-pattern
- Indexing every column individually
- Missing composite indexes for common filters
Better approach
Suppose this query is common:
SELECT id, user_id, status, created_at
FROM orders
WHERE tenant_id = ? AND status = ?
ORDER BY created_at DESC
LIMIT 50;
Use a composite index in query order:
CREATE INDEX idx_orders_tenant_status_created
ON orders (tenant_id, status, created_at DESC);
For Laravel migrations:
Schema::table('orders', function (Blueprint $table) {
$table->index(['tenant_id', 'status', 'created_at'], 'idx_orders_tenant_status_created');
});
That single index can remove filesorts and full scans in high-traffic endpoints.
4) Validate with EXPLAIN every time
Don’t trust intuition. Run:
EXPLAIN ANALYZE
SELECT id, user_id, status, created_at
FROM orders
WHERE tenant_id = 25 AND status = 'paid'
ORDER BY created_at DESC
LIMIT 50;
Look for:
type = ref/range(better thanALL)- low rows examined
- no unexpected temporary tables/filesort
If query plans are still poor, revisit:
- index column order
- selectivity
- unnecessary joins
5) Benchmarks from a real optimization cycle
Below are two real endpoint improvements after removing N+1 + applying index fixes.
| Endpoint | Before | After | Main fixes |
|---|---|---|---|
GET /api/dashboard |
843ms | 67ms | Eager loading + reduced selected columns + composite indexes |
GET /api/orders |
1240ms | 89ms | N+1 removal + optimized WHERE/ORDER index + query shape simplification |
6) Operational safeguards to keep performance from regressing
Optimization is not a one-time task. Add guardrails.
A) Slow query alerts
Alert on query durations crossing your threshold (e.g., 100ms in prod).
B) Query count assertions in tests
public function test_orders_endpoint_query_count_is_bounded(): void
{
DB::enableQueryLog();
$this->getJson('/api/orders')->assertOk();
$queries = DB::getQueryLog();
$this->assertLessThanOrEqual(12, count($queries));
}
C) Release checklist
EXPLAINfor all changed heavy queries- Verify index usage in staging data size
- Compare before/after p95 latency
For broader production practices, pair this with Laravel API development best practices and AWS cost optimization for Laravel apps.
7) A practical laravel database optimization workflow
Use this sequence whenever an endpoint slows down:
- Profile endpoint and capture baseline latency
- List SQL queries and count total queries per request
- Remove N+1 via
with(),withCount(), and relationship shaping - Add/adjust composite indexes for real filter/sort patterns
- Validate query plan with
EXPLAIN ANALYZE - Re-test with production-like data volume
- Add tests/alerts to prevent regressions
If you’re modernizing older code, this pairs well with migrating legacy Laravel apps safely.
8) Advanced tips for high-scale systems
- Prefer cursor pagination for large sequential scans
- Use read replicas for heavy read workloads
- Move expensive aggregations to background jobs or precomputed tables
- Cache hot responses (carefully) after query optimization
- Keep schema/index migrations reviewed like application code
For teams using AI-assisted development workflows, documenting query patterns and index rationale also improves code reviews and tool suggestions—similar to the process in Claude Code workflow posts.
Final thoughts
Most Laravel performance issues aren’t solved with bigger servers.
They’re solved with better query shape, relationship loading, and index strategy.
If you focus first on N+1 query laravel fixes, then apply deliberate mysql indexing laravel improvements validated by query plans, getting sub-100ms responses is realistic for many endpoints.
Start with one slow endpoint this week and run the exact workflow above. You’ll likely get a bigger gain than any framework-level tweak.

Richard Joseph Porter
Senior Laravel Developer with 14+ years of experience building scalable web applications. Specializing in PHP, Laravel, Vue.js, and AWS cloud infrastructure. Based in Cebu, Philippines, I help businesses modernize legacy systems and build high-performance APIs.
Need Help Upgrading Your Laravel App?
I specialize in modernizing legacy Laravel applications with zero downtime. Get a free codebase audit and upgrade roadmap.
Related Articles
Laravel Octane: Achieving Sub-50ms Response Times
Master Laravel Octane with Swoole, RoadRunner, and FrankenPHP. Learn memory leak prevention, production deployment patterns, and benchmarks for high-performance apps.
Migrate WordPress to Laravel: The Complete Developer Guide
Step-by-step guide to migrating WordPress sites to Laravel. Cover content migration, database design, SEO preservation, and custom feature implementation.
Senior Laravel Developer Interview Questions: A Complete Guide
Master 20 technical interview questions for senior Laravel roles. Learn what interviewers look for and how to articulate your expertise clearly.