SQL Queries: Stop Killing Your Database
Your queries are slow, inefficient, and killing the database. Here's how to fix them without rewriting everything.
Table of Contents
- The N+1 Problem
- SELECT Only What You Need
- Avoid SELECT in Joins
- Use WHERE Before JOIN
- Limit Results
- Avoid OR in WHERE
- Use EXISTS Instead of COUNT
- Chunk Large Datasets
- Real Project Example
- Query Analysis
- Use EXPLAIN
- Laravel Query Log
- Slow Query Log
- Common Mistakes
- Mistake 1: Using LIKE with Leading Wildcard
- Mistake 2: Functions in WHERE
- Mistake 3: Not Using Indexes
- Optimization Checklist
- Bottom Line
SQL Queries: Stop Killing Your Database
Your database is at 90% CPU. Queries taking 10 seconds. Users complaining.
Here's how to fix it.
The N+1 Problem
Most common performance killer:
// Get all orders
$orders = Order::all();
// Get customer for each order
foreach ($orders as $order) {
echo $order->customer->name; // Query for EACH order
}
100 orders = 101 queries (1 for orders + 100 for customers).
Fix: Eager Loading
$orders = Order::with('customer')->get();
foreach ($orders as $order) {
echo $order->customer->name; // No extra queries
}
100 orders = 2 queries (1 for orders + 1 for all customers).
Real impact: Dashboard load time 8s → 0.4s.
SELECT Only What You Need
// Bad: Gets all columns
$users = User::all();
// Good: Gets only needed columns
$users = User::select('id', 'name', 'email')->get();
Why it matters:
Table with 20 columns, 100,000 rows:
- SELECT *: 50MB transferred
- SELECT id, name: 5MB transferred
10x less data = 10x faster.
Avoid SELECT * in Joins
// Bad: Gets all columns from both tables
$orders = DB::table('orders')
->join('users', 'orders.user_id', '=', 'users.id')
->select('*')
->get();
// Good: Specify columns
$orders = DB::table('orders')
->join('users', 'orders.user_id', '=', 'users.id')
->select('orders.*', 'users.name', 'users.email')
->get();
Use WHERE Before JOIN
// Bad: Joins all, then filters
SELECT orders.*, users.name
FROM orders
JOIN users ON orders.user_id = users.id
WHERE orders.status = 'pending';
// Good: Filters first, then joins
SELECT orders.*, users.name
FROM orders
WHERE orders.status = 'pending'
JOIN users ON orders.user_id = users.id;
Database processes fewer rows.
Limit Results
// Bad: Gets everything
$products = Product::where('category', 'electronics')->get();
// Good: Limits results
$products = Product::where('category', 'electronics')
->limit(20)
->get();
Need pagination? Even better:
$products = Product::where('category', 'electronics')
->paginate(20);
Avoid OR in WHERE
// Slow: Can't use indexes efficiently
SELECT * FROM products
WHERE category = 'electronics'
OR category = 'computers'
OR category = 'phones';
// Fast: Uses index
SELECT * FROM products
WHERE category IN ('electronics', 'computers', 'phones');
Laravel:
// Slow
Product::where('category', 'electronics')
->orWhere('category', 'computers')
->orWhere('category', 'phones')
->get();
// Fast
Product::whereIn('category', ['electronics', 'computers', 'phones'])
->get();
Use EXISTS Instead of COUNT
// Slow: Counts all matching rows
if (Order::where('user_id', $userId)->count() > 0) {
// User has orders
}
// Fast: Stops at first match
if (Order::where('user_id', $userId)->exists()) {
// User has orders
}
Chunk Large Datasets
// Bad: Loads 1 million rows into memory
$users = User::all();
foreach ($users as $user) {
$user->sendEmail();
}
// Good: Processes in chunks
User::chunk(1000, function ($users) {
foreach ($users as $user) {
$user->sendEmail();
}
});
Real Project Example
Client: E-commerce site, product listing page taking 12 seconds.
Original query:
$products = Product::with('category', 'images', 'reviews')
->where('status', 'active')
->get();
Problems:
- Loading all products (50,000+)
- Loading all images (200,000+)
- Loading all reviews (500,000+)
- No pagination
Optimized query:
$products = Product::select('id', 'name', 'price', 'category_id')
->with([
'category:id,name',
'images' => fn($q) => $q->select('id', 'product_id', 'url')->limit(1),
])
->withCount('reviews')
->withAvg('reviews', 'rating')
->where('status', 'active')
->paginate(20);
Results:
- Load time: 12s → 0.3s
- Memory usage: 500MB → 15MB
- Database CPU: 85% → 20%
Query Analysis
Use EXPLAIN
EXPLAIN SELECT * FROM orders WHERE user_id = 123;
Look for:
type: ALL= Full table scan (bad)type: ref= Using index (good)rows: 1000000= Scanning too many rows
Laravel Query Log
DB::enableQueryLog();
// Your code here
dd(DB::getQueryLog());
Shows all queries executed. Find slow ones.
Slow Query Log
-- MySQL
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;
-- Check slow queries
SELECT * FROM mysql.slow_log;
Common Mistakes
Mistake 1: Using LIKE with Leading Wildcard
// Slow: Can't use index
Product::where('name', 'LIKE', '%phone%')->get();
// Fast: Can use index
Product::where('name', 'LIKE', 'phone%')->get();
For full-text search, use full-text index:
Product::whereRaw('MATCH(name) AGAINST(?)', ['phone'])->get();
Mistake 2: Functions in WHERE
// Slow: Can't use index
SELECT * FROM orders WHERE DATE(created_at) = '2026-03-01';
// Fast: Uses index
SELECT * FROM orders
WHERE created_at >= '2026-03-01 00:00:00'
AND created_at < '2026-03-02 00:00:00';
Mistake 3: Not Using Indexes
// Add index for frequently queried columns
Schema::table('orders', function (Blueprint $table) {
$table->index('user_id');
$table->index('status');
$table->index(['user_id', 'status']);
});
Optimization Checklist
- Use eager loading (avoid N+1)
- SELECT only needed columns
- Add indexes for WHERE/JOIN columns
- Use pagination
- Avoid SELECT * in joins
- Use EXISTS instead of COUNT
- Chunk large datasets
- Cache expensive queries
- Monitor slow queries
- Use EXPLAIN to analyze
Bottom Line
Most performance issues are bad queries.
Eager load relationships. Select only what you need. Add indexes. Paginate.
These simple changes can make queries 100x faster.
Need database optimization?
We fix slow databases for Nigerian businesses. Query optimization, indexing, performance tuning.
📞 WhatsApp: +234 708 711 0468
📧 info@raspibtech.com
📍 Lagos Island
Related:
Need Help with Your Project?
Let's discuss how Raspib Technology can help transform your business
Related Articles
Laravel 11: What Changed and Why You Should Care
Laravel 11 is out. Slimmer structure, better performance, and features that actually save time. Here's what matters.
Read more →Laravel 12: The Upgrade You've Been Waiting For
Laravel 12 brings major improvements. Here's what changed and why it matters for your projects.
Read more →Next.js 15: The Features That Actually Matter
Next.js 15 changed a lot. Here's what affects your projects, what breaks, and when to upgrade.
Read more →