- Knowledge
- technology
- OOP
- Tips
- Programming
- Tips
- Tutorial
- SEO
- Ranking
- Knowledge
- Special Day
- Seo
- Bug
- Data science
- Seo
- artificial intelligence
- Machine Learning
- Robotics
- happyNewYear2021
- newYearEve
- 2021
- Automation
- Smart Home
- Career
- Best Practices
- Git
- Logging
- Web Fundamentals
- DNS
- HTTPS
- Performance
- AI Tools
- ChatGPT
- Claude
- Gemini
- Laravel
- Eloquent
- MySQL
- HTTPS
- TLS
- Web Security
- Certificates
- Developer Life
- Debugging
- Docker
- DevOps
- Transactions
- Queues
- LLMs
- AI
- AI Coding
- Developer Tools
- React Native
- Expo
- Kate PMS
- Mobile Apps
- Laravel
- Authentication
- Sanctum
- Cookies
- API Design
- Payments
- Idempotency
- DeepSeek
- Open Source AI
- LLMs
- AI News
- Git
- Version Control
- AI Coding
- Prompting
- PHP
- Checklist
- MCP
- AI Agents
- OpenAI
- Architecture
- Microservices
- Modular Monolith
- Estimation
- Developer Life
- Project Planning
- Humour
- OAuth
- OpenID Connect
- Authentication
- Embeddings
- Vector Search
- RAG
- pgvector
- OpenAI
- GPT-4.1
- Codex CLI
- Events
- Testing
- Clean Code
- Maintainability
- Code Review
- Webhooks
- API
- Security
- Claude Code
- Workflow
- AI
- LLM
- Prompt Injection
- Mobile
- React
- Networking
- TCP
- UDP
- HTTP/3
- CLAUDE.md
- AWS
- Cloud Security
- Backups
- PHPUnit
- Software Engineering
- Leadership
- Communication
- RAG
- Embeddings
- AI Engineering
- IT Infrastructure
- Networking
- Access Control
- CI/CD
- GitHub Actions
- Gemini CLI
- Claude Code
- JavaScript
- Async/Await
- Node.js
- Promises
- Security
- Cryptography
- Passwords
- MySQL
- Database
- Vibe Coding
- Software Quality
- DNS
- Code Reading
- Onboarding
- Productivity
- Background Jobs
- Developer Humour
- Estimates
- Dev Life
- JWT
- o3-mini
- DeepSeek R1
- Rate Limiting
- Kate PMS
- E-Signing
- Audit Trail
- REST
- GraphQL
- API Design
- Laravel 12
- Upgrade Guide
- Open Source
- Self-Hosting
- Task Scheduling
- Cron
- Secrets
- CORS
- PHP
- PHP-FPM
- OPcache
- GitHub Copilot
- Software Architecture
- Engineering
- TypeScript
- JavaScript
- Type Safety
- AI Security
- React Native
- Product Design
- AI Agents
- Kiro
- Queues
- Redis
- RabbitMQ
- AWS SQS
- Nginx
- Apache
- GPT-5
- gpt-oss
- Clean Code
- Architecture
- Naming
- Documentation
- Career
- ADR
- Teamwork
- Supply Chain
- Kate HRM
- HR Software
- Permissions
- System Design
- Pagination
- SSH
- Linux
- Big O
- Databases
- Laravel Boost
- MCP
- Developer Skills
- Validation
- Databases
- Indexes
- Code Quality
- Deployment
- Developer Humour
- Feature Flags
- Code Review
- Pull Requests
- Docker
- Cursor
- Authorization
- RBAC
- Gemini
- Long Context
- PHP 8.4
- Caching
- Dependency Injection
- Web Performance
- Browser
- CSS
- Database
- Migrations
- ChatGPT
- AI for Developers
- Monitoring
- On-Call
- REST
- Backend
- SQL
- NoSQL
- Database Design
- Coding Agents
- Claude 4
- API Resources
- REST API
- Load Balancing
- Scaling
- AWS
- AI Tools
- Claude
- Sora 2
- CTE
- 2FA
- TOTP
- Programming Languages
- Prompts
- Developer Workflow
- API Gateway
- APIs
- Passport
- API Auth
- Learning
- Burnout
- Developer Growth
- Web Development
- SEO
- Kate Mall
- ChatGPT Atlas
- Agent Skills
- Middleware
- Laravel 12
- Collections
- Context Window
- Monitoring
- Commit Messages
- Self Review
- Growth
- Regex
- Programming Basics
- Text Processing
- Database Design
- Normalization
- Linux
- Server Security
- Linux Foundation
- Open Standards
- Legacy Code
- Documentation
- AI Workflow
- File Uploads
- Test Data
- Hashing
- Performance
- Caching
- Enums
- Scope Creep
- Estimation
- Codex
- Gemini CLI
- Timezones
- Carbon
- Bugs
- PHP 8.5
- Gemini 3
- GPT-5.1
- Data Integrity
- Event Loop
- Async
- Opus 4.5
- AI Models
- React
- Forms
- Frontend
- Backups
- AI Images
- DALL-E
- Midjourney
- Race Conditions
- Concurrency
- Legacy Code
- Refactoring
- Senior Engineer
- Scope
- LLM
- CDN
- Web
- Sub-Agents
- Soft Deletes
- Audit Log
- Concurrency
- AI Learning
- NestJS
- AI Evals
- Policies
- SPF DKIM DMARC
- Unicode
- UTF-8
- Knowledge Graph
- Value Objects
- Technical Debt
- Feature Flags
- Laravel Pennant
- Deployment
- Copilot
- Composer
- Dependencies
- Artisan
- Automation
- AWS S3
- Object Storage
- Cloud
- Small Language Models
- Ollama
- Production
- Sessions
- HTTP
- Mentoring
- SQL
- Virtual Machines
- Web Development
- HTTP/2
- QUIC
- Web Performance
- AI Integration
- LLM API
- SOLID
- OOP
- Hosting
- Serverless
- Merge Conflicts
- Temperature
- AI Development
- Reverse Proxy
- Nginx
- Infrastructure
- Verification
- Passkeys
- WebAuthn
- Teams
- Communication
- Stakeholders
- Monorepo
- CI/CD
- Versioning
- JSON Schema
- Livewire
- Inertia
- Meetings
- Distributed Systems
- Privacy
- Full-Stack
- T-Shaped Skills
- Money
- Notifications
- Web Security
- HTTP Headers
- CSP
- Function Calling
- Load Testing
- k6
- Data Extraction
- Debugging
- WebSockets
- SSE
- Real-Time
- Laravel Reverb
- Infrastructure as Code
- Terraform
- Side Projects
- Laravel Pint
- OpenAPI
- Swagger
- UX
- Multimodal
- Jest
- Pair Programming
- APIs
- Rate Limiting
- Resilience
- Dev Humour
- Design Tokens
- JWT
- API Keys
- Sessions
- PHPStan
- Rector
- Incidents
- Reporting
- Dashboards
- Zero Trust
- IAM
- Search
- Laravel Scout
- Junior Developers
- Mentoring
- Images
- WebP
- AVIF
- Bug Reports
- Let's Encrypt
- Design Docs
- Software Design
- Observers
- Replication
- Accountability
- Data Structures
- Reliability
- LLM Memory
- Error Handling
- Payments
- Payment Gateway
- Webhooks
- PCI DSS
- Observability
- OpenTelemetry
- Personal Brand
- Writing
- Conventions
- Dates
- Scheduling
- Disaster Recovery
- Compression
- Brotli
- Deadlines
- Developer Habits
- State Machines
- Tech Roles
- UUID
- ULID
- Horizon
- Planning
- Engineering Culture
- Ownership
- Soft Skills
- Socialite
- Cost Control
- Collations
- Unicode
- Octane
- PostgreSQL
Seven MySQL Indexing Mistakes That Quietly Slow Down Your App
About Post
You found the slow query. You added an index. You ran it again, full of hope.
Still slow.
An index is not a magic speed switch. It's a sorted structure that MySQL can only use when your query is shaped the right way. Shape it wrong and MySQL politely ignores your index and reads the whole table, every time, without telling you. These are the seven mistakes I see most often, in roughly the order they tend to bite.
First, a 30-second mental model
A B-tree index is like the index at the back of a book, sorted by the indexed value. If you know the exact word, or a range of words starting with "con", you can jump straight to the right page. If you're looking for every word that ends in "tion", the sorted order is useless and you're back to reading every page.
Almost every mistake below is a version of that: asking a question the sorted order can't answer.
1. Wrapping the column in a function
This one looks completely innocent:
-- Can't use an index on created_at
SELECT * FROM payments WHERE YEAR(created_at) = 2024;
SELECT * FROM users WHERE LOWER(email) = '[email protected]';
The index stores created_at values, not YEAR(created_at) values. To answer the question MySQL has to compute the function for every row. Rewrite it as a range on the raw column:
SELECT * FROM payments
WHERE created_at >= '2024-01-01' AND created_at < '2025-01-01';
Same result, and now it's an index range scan. In Laravel, watch out for whereYear() and whereDate(): they're convenient, but they generate exactly this function-wrapped SQL. whereBetween() or two where() calls keep the index usable. (The LOWER(email) case usually isn't needed at all: MySQL's default collations are already case-insensitive. If you truly need it, MySQL 8.0.13+ supports functional indexes.)
2. Searching with a leading wildcard
WHERE name LIKE 'Moh%' can use an index. WHERE name LIKE '%med' cannot. That's the book-index problem exactly: the values are sorted from the first character, so a pattern that starts with "anything" gives MySQL nowhere to begin.
If you need "contains" search on real text, look at a FULLTEXT index or a proper search engine. If it's a small table, accept the scan and stop worrying.
3. Composite index columns in the wrong order
An index on (tenant_id, status, created_at) is sorted by tenant_id first, then status within each tenant, then date. MySQL can use it from the left: tenant_id, or tenant_id + status, or all three. It cannot jump in at status alone. This is the leftmost prefix rule.
The practical rule of thumb for ordering:
- Columns you filter with equality (
=,IN) go first. - The column you filter by range or sort by goes last. Once MySQL hits a range, the columns after it can't be used for seeking.
// Query: where('tenant_id', $id)->where('status', 'open')->latest()
$table->index(['tenant_id', 'status', 'created_at']);
4. Indexing a column with only a few distinct values
An index on is_active where most rows are active barely helps. If the condition matches a large share of the table, reading the index and then jumping back to the table for each row is slower than a straight scan, and the optimizer knows it. It will often just skip your index.
Low-selectivity columns still earn their place, but as part of a composite index next to something selective, not on their own.
5. Adding an index for every query
The opposite mistake. Every index is a second copy of some data that must be updated on every INSERT, UPDATE and DELETE. It also costs memory in the buffer pool. Ten single-column indexes on one table make writes slower and usually help reads less than two well-designed composite ones.
Look for redundancy: if you have (tenant_id, status), a separate index on tenant_id alone is usually dead weight.
6. Comparing different types
This one is sneaky. A phone column is a VARCHAR, and the query does this:
SELECT * FROM tenants WHERE phone = 97455501234; -- number, not string
When MySQL compares a string column with a number, it converts the column values to numbers, row by row. The index on phone becomes useless. Quote the value and it's instant again. The same family of problems shows up when joining columns with different character sets or collations, or a signed INT to a BIGINT UNSIGNED foreign key. Keep the types identical on both sides.
7. Guessing instead of running EXPLAIN
The real mistake behind the other six is not checking. EXPLAIN tells you what MySQL actually plans to do:
EXPLAIN SELECT * FROM payments
WHERE tenant_id = 42 AND status = 'open'
ORDER BY created_at DESC;
The columns worth reading first:
- type:
ALLmeans a full table scan.ref,rangeandconstare what you want. - key: the index it chose.
NULLmeans none. - rows: an estimate of how many rows it will examine.
- Extra:
Using filesortorUsing temporaryare hints that your index doesn't cover the sort.
On MySQL 8.0.18 and later, EXPLAIN ANALYZE actually runs the query and shows real timings per step, which is even better.
The rule I follow: design indexes from your real queries, not from your columns. Write the query first, run EXPLAIN, then add the smallest index that turns ALL into ref or range.
A quick checklist before you add the next index
- Is the indexed column used raw, without a function around it?
- Does any
LIKEpattern start with a fixed prefix? - Are equality columns first and the range or sort column last?
- Is the column selective enough to be worth it on its own?
- Does an existing composite index already cover it?
- Do the types and collations match exactly?
- What does EXPLAIN say, before and after?
Which of these has cost you the most time? My vote for the sneakiest is the type mismatch, because the query looks perfect on paper. I'm curious which one got you.

Be first to comment it...