๐ข️ 18 SQL Anti-Patterns That Are Slowing Down Your Database
SQL performance problems often arise from common anti-patterns. Understanding these 10 mistakes will help you write faster, cleaner, and more maintainable SQL queries.
1. ๐ซ SELECT * (Lazy Column Fetching)
Selecting all columns fetches unnecessary data and increases memory and network load.
-- BAD
SELECT * FROM users;
-- GOOD
SELECT id, username FROM users;
✅ Always select only the columns you need.
2. ๐ฆ Storing Comma-Separated Values
Storing multiple values in one column violates normalization and makes queries slow and complex.
-- BAD
-- hobbies column: "reading,swimming,gaming"
SELECT * FROM users WHERE hobbies LIKE '%swimming%';
-- GOOD: normalized table
CREATE TABLE user_hobbies (
user_id INT,
hobby VARCHAR(50)
);
✅ Normalize data for better performance and maintainability.
3. ๐ Using LIKE with Leading Wildcards
Leading wildcards prevent indexes from being used, causing full table scans.
-- BAD
SELECT * FROM products WHERE name LIKE '%phone';
-- GOOD
SELECT * FROM products WHERE name LIKE 'phone%';
✅ Avoid leading wildcards in LIKE queries; consider full-text search for complex patterns.
4. ๐งฑ Ignoring Indexes
Queries on unindexed columns require full table scans, which slow down large tables.
-- Create an index on frequently queried columns
CREATE INDEX idx_user_id ON orders(user_id);
✅ Index columns used in WHERE, JOIN, and ORDER BY clauses.
5. ๐ Using Subqueries Instead of Joins
Subqueries are often less efficient and harder to optimize than joins.
-- BAD
SELECT * FROM employees
WHERE department_id IN (SELECT id FROM departments WHERE name='Sales');
-- GOOD
SELECT e.* FROM employees e
JOIN departments d ON e.department_id = d.id
WHERE d.name = 'Sales';
✅ Prefer joins for better performance and readability.
6. ๐งฎ Using COUNT(*) on Large Tables
Counting all rows scans the entire table and locks resources.
-- BAD
SELECT COUNT(*) FROM large_table;
-- GOOD
SELECT COUNT(id) FROM large_table;
✅ Count indexed columns instead of *, especially on large tables.
7. ๐ง Using NULL for Missing Data
NULL values complicate calculations and logic.
-- BAD
SELECT * FROM users WHERE age IS NULL;
-- GOOD: use default value or flag
SELECT * FROM users WHERE age = 0; -- or add 'is_unknown' flag
✅ Use default values or flags instead of NULL when possible.
8. ๐ Repeatedly Writing the Same Query
Repetition increases maintenance effort and can slow down development.
-- BAD
SELECT * FROM users WHERE status='active';
SELECT * FROM users WHERE status='inactive';
-- GOOD
SELECT * FROM users WHERE status IN ('active','inactive');
✅ Consolidate similar queries for cleaner, faster code.
9. ๐งฉ Not Using Constraints
Lack of constraints allows invalid data, making queries unreliable and slow.
-- BAD: no foreign key
SELECT * FROM orders WHERE customer_id = 123;
-- GOOD
ALTER TABLE orders ADD CONSTRAINT fk_customer
FOREIGN KEY (customer_id) REFERENCES customers(id);
✅ Use primary keys, foreign keys, and unique constraints to enforce data integrity.
10. ๐งช Using Transactions Inefficiently
Long-running transactions lock resources and can cause deadlocks.
-- BAD
BEGIN TRANSACTION;
UPDATE users SET status='active';
UPDATE orders SET status='processed';
COMMIT;
-- GOOD
BEGIN TRANSACTION;
UPDATE users SET status='active';
COMMIT;
BEGIN TRANSACTION;
UPDATE orders SET status='processed';
COMMIT;
✅ Keep transactions short and commit promptly.
11. ๐ Overusing DISTINCT
Using DISTINCT
forces the database to sort or hash all rows to remove duplicates, which can be very expensive on large tables.
-- BAD
SELECT DISTINCT username FROM users;
-- GOOD: remove duplicates at insertion or use GROUP BY if aggregation is needed
SELECT username FROM users
GROUP BY username;
✅ Avoid DISTINCT when possible; fix duplicates at the data level.
12. ⚖️ Inefficient JOIN Order or Missing Indexes on Join Keys
Joining large tables without indexing the join columns can cause full table scans and slow queries.
-- BAD
SELECT *
FROM orders o
JOIN customers c ON o.customer_id = c.id;
-- GOOD: Ensure customer_id is indexed
CREATE INDEX idx_customer_id ON orders(customer_id);
✅ Always index join columns and check the execution plan for performance.
13. ๐ณ Using Functions on Indexed Columns in WHERE
Functions like UPPER()
, TRIM()
, or DATE()
on indexed columns prevent the database from using the index.
-- BAD
SELECT * FROM users WHERE UPPER(username) = 'ADMIN';
-- GOOD
-- Store usernames in uppercase or use generated columns
SELECT * FROM users WHERE username = 'ADMIN';
✅ Avoid functions on indexed columns in WHERE clauses.
14. ๐ข Inefficient Pagination
Using OFFSET
with large tables can cause the database to scan all previous rows.
-- BAD
SELECT * FROM orders ORDER BY created_at LIMIT 50 OFFSET 100000;
-- GOOD: Keyset pagination
SELECT * FROM orders
WHERE created_at > '2025-09-01'
ORDER BY created_at
LIMIT 50;
✅ Use keyset pagination for large datasets.
15. ๐ Not Using Batching for Inserts/Updates
Inserting or updating one row at a time creates excessive I/O and transaction overhead.
-- BAD
INSERT INTO users (username) VALUES ('user1');
INSERT INTO users (username) VALUES ('user2');
-- GOOD: batch insert
INSERT INTO users (username) VALUES ('user1'), ('user2'), ('user3');
✅ Batch operations improve throughput and reduce disk overhead.
16. ๐ Not Archiving Old Data
Large, unpartitioned tables slow down queries as the dataset grows.
-- GOOD PRACTICE
-- Move historical orders to a separate table or partition
INSERT INTO orders_archive SELECT * FROM orders WHERE created_at < '2023-01-01';
DELETE FROM orders WHERE created_at < '2023-01-01';
✅ Archive or partition old data to keep tables fast.
17. ๐ Holding Locks Too Long
Long-running transactions block other queries, decreasing concurrency and causing deadlocks.
-- BAD
BEGIN TRANSACTION;
UPDATE users SET status='active';
-- transaction remains open for other operations
COMMIT;
-- GOOD
BEGIN TRANSACTION;
UPDATE users SET status='active';
COMMIT;
✅ Keep transactions short and commit promptly.
18. ๐ Ignoring Execution Plans
Writing queries without analyzing their execution plans can hide bottlenecks and inefficiencies.
-- Example
EXPLAIN SELECT * FROM orders WHERE customer_id = 123;
✅ Use EXPLAIN
or EXPLAIN ANALYZE
to optimize queries and check if indexes are used.
Conclusion
Avoiding these 18 SQL anti-patterns improves performance, reduces database load, and prevents maintenance headaches. Always consider **indexes, normalization, and efficient query design** when writing SQL for production.
Labels: SQL, Database, Performance, Optimization, Best Practices, Queries
Comments
Post a Comment