Top SQL Performance Anti-Patterns in PostgreSQL and MySQL (2026 Guide)
As modern web applications scale, database bottlenecks are frequently caused by subtle SQL query anti-patterns that prevent the database optimizer from using indexes. Regardless of whether you run PostgreSQL 16+ or MySQL 8.4+, these persistent coding habits force full table scans, exhaust buffer pools, and trigger transaction timeouts.
1. Non-SARGable Predicates (Wrapping Indexed Columns in Functions)
A SARGable (Search Argument Able) query enables the database engine to perform an efficient index seek. Applying a function or calculation directly to an indexed column invalidates the index:
-- ❌ Anti-Pattern: Forces full table scan
SELECT id, email, created_at
FROM users
WHERE EXTRACT(YEAR FROM created_at) = 2026;
-- ✅ High-Performance Fix: Range filter seeks directly into B-tree index
SELECT id, email, created_at
FROM users
WHERE created_at >= '2026-01-01' AND created_at < '2027-01-01';
2. The Deep OFFSET Pagination Problem
Using large OFFSET values in APIs or UI infinite
scrolls causes quadratic overhead:
-- ❌ Anti-Pattern: Scans and discards 100,000 rows before returning 20
SELECT * FROM transactions
ORDER BY id DESC
LIMIT 20 OFFSET 100000;
-- ✅ Keyset Pagination (Seek Method): Constant O(1) performance
SELECT * FROM transactions
WHERE id < 452910
ORDER BY id DESC
LIMIT 20;
3. Implicit Type Casting
When a query compares a column of one data type against an incompatible literal, the engine automatically converts every row in the column, disabling index scans:
-- Assuming account_number is VARCHAR
-- ❌ Forces full scan due to integer-to-string cast per row
SELECT * FROM accounts WHERE account_number = 10048291;
-- ✅ Type-aligned: Instant index lookup
SELECT * FROM accounts WHERE account_number = '10048291';
4. Leading Wildcards in LIKE Searches
A leading wildcard (LIKE '%query') prevents the
B-Tree index from traversing branches from left to right.
-
For prefix matches: Use trailing wildcards
only:
WHERE username LIKE 'dev%'. -
For substring search: Use PostgreSQL
GINindexes withpg_trgmor MySQLFULLTEXTindexes.
5. The N+1 Query ORM Hazard
ORMs (Prisma, TypeORM, Hibernate) often query parent records, then
fire $N$ individual queries in a loop to retrieve related
children. Always utilize explicit eager loading (JOIN FETCH
or batch IN (...) queries) to collapse round-trips
into a single statement.
Format and Validate SQL Queries Client-Side
Need to beautify raw SQL queries, inspect table structures, or validate syntax across PostgreSQL, MySQL, and SQLite without leaking proprietary queries to third-party APIs? Use our free in-browser SQL Formatter & Validator Tool.