惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Martin Fowler
Martin Fowler
WordPress大学
WordPress大学
月光博客
月光博客
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
大猫的无限游戏
大猫的无限游戏
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
博客园 - 聂微东
Apple Machine Learning Research
Apple Machine Learning Research
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
雷峰网
雷峰网
小众软件
小众软件
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - 叶小钗
美团技术团队
宝玉的分享
宝玉的分享
Hugging Face - Blog
Hugging Face - Blog
阮一峰的网络日志
阮一峰的网络日志
A
About on SuperTechFans
Jina AI
Jina AI
D
Docker
Last Week in AI
Last Week in AI
MongoDB | Blog
MongoDB | Blog
Stack Overflow Blog
Stack Overflow Blog
Microsoft Azure Blog
Microsoft Azure Blog

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Laravel whereDate() Silently Kills Your Index
Ivan Mykhavk · 2026-04-23 · via DEV Community

whereDate('created_at', $date) looks clean, but on a big table it quietly drops your index and does a full scan.

The Problem

Say you want notifications created on a specific day. The obvious call:

UserNotification::query()
    ->whereDate('created_at', '2026-04-23')
    ->get();

Enter fullscreen mode Exit fullscreen mode

Laravel generates this SQL:

SELECT * FROM user_notifications
WHERE DATE(created_at) = '2026-04-23'

Enter fullscreen mode Exit fullscreen mode

See DATE(created_at)? MySQL has to compute that function for every row before comparing. Your created_at index is useless, EXPLAIN shows a full table scan:

+----+-------------+--------------------+------+---------+
| id | select_type | table              | type | rows    |
+----+-------------+--------------------+------+---------+
|  1 | SIMPLE      | user_notifications | ALL  | 5000000 |
+----+-------------+--------------------+------+---------+

Enter fullscreen mode Exit fullscreen mode

On 5k rows you won't notice. On 5M rows you will.

The Solution

Compare the column directly against a range:

use Illuminate\Support\Facades\Date;

$start = Date::parse('2026-04-23')->startOfDay();
$end = $start->copy()->addDay();

UserNotification::query()
    ->where('created_at', '>=', $start)
    ->where('created_at', '<', $end)
    ->get();

Enter fullscreen mode Exit fullscreen mode

Now the SQL looks like this:

SELECT * FROM user_notifications
WHERE created_at >= '2026-04-23 00:00:00'
  AND created_at <  '2026-04-24 00:00:00'

Enter fullscreen mode Exit fullscreen mode

The column is untouched. MySQL can do a clean range scan on the created_at index:

+----+-------------+--------------------+-------+------+
| id | select_type | table              | type  | rows |
+----+-------------+--------------------+-------+------+
|  1 | SIMPLE      | user_notifications | range | 1200 |
+----+-------------+--------------------+-------+------+

Enter fullscreen mode Exit fullscreen mode

Half-open range (>= start, < next day) is the safer form - endOfDay() ends at 23:59:59.999999, and comparing against 23:59:59 can quietly miss the last second.

Why It Works

This is called sargability - "Search ARGument ABLE". A predicate is sargable when the column appears as-is, without a function wrapping it. The moment you write DATE(col), YEAR(col), or LOWER(col), the optimizer can't use a standard B-tree index on col anymore.

The same trap applies to whereDay(), whereMonth(), whereYear(), and whereTime() all of them wrap the column in a MySQL function. Fine on small lookup tables. Painful on any growing log-style table.

Heads up: PostgreSQL lets you build a functional index (CREATE INDEX ON t ((date(created_at)))), so there whereDate() can still hit an index. MySQL has no real equivalent for this case, generated columns with an index work, but they're extra schema baggage for something a range filter already solves.

Tutorials love whereDate('created_at', today()). I still prefer the range form. It reads the same everywhere, and I never have to wonder whether an index will be used.

TL;DR

whereDate() is convenient but non-sargable on large tables it forces a full scan. Compare created_at against a half-open >= startOfDay() / < startOfDay() + 1 day range and keep the index in play.

💡 Same story for whereMonth(), whereYear(), whereDay(), whereTime(). If the column is wrapped in a function, assume the index is gone.

Author's Note

Thanks for sticking around!
Find me on dev.to, linkedin, or you can check out my work on github.

Notes from real-world Laravel.