SQL Query Optimization Practical Complete Guide
이 글의 핵심
Index design, EXPLAIN, and common bottlenecks in N+1, joins, and subqueries. Practical guide to reducing slow queries. Focused on SQL, query optimizatio...
🎯 What You’ll Learn (Reading Time: 30 minutes)
TL;DR: Learn how to improve SQL query performance 10x. Master immediately applicable optimization techniques from index design to EXPLAIN analysis and N+1 problem resolution. What You’ll Learn:
- ✅ Master index design principles and practical application
- ✅ Acquire query execution plan analysis skills with EXPLAIN
- ✅ Solve anti-patterns like N+1 problems and join optimization
- ✅ Learn performance improvement know-how from real optimization cases Real-World Applications:
- 🔥 Make slow queries 10x faster
- 🔥 Reduce database load (cut server costs)
- 🔥 Optimize large-scale data processing
- 🔥 Improve API response times Difficulty: Intermediate | Practical Examples: 15 | Performance Comparisons: Included
Core Message
Reducing one scan is reducing cost. Focus on indexes, EXPLAIN, and common anti-patterns (N+1, etc.).
Table of Contents
- Why Query Optimization Matters
- Index Basics
- Analyzing Queries with EXPLAIN
- Index Design Strategy
- Solving N+1 Problem
- Join Optimization
- Subquery Optimization
- Practical Optimization Cases
Prerequisites (Basics for Beginners)
1. Database Table Structure
Tables store data in rows and columns.
-- users table
CREATE TABLE users (
id INT PRIMARY KEY, -- Unique number
name VARCHAR(100), -- Name
email VARCHAR(100), -- Email
created_at TIMESTAMP -- Registration date
);
-- Data example
+----+--------+-------------------+---------------------+
| id | name | email | created_at |
+----+--------+-------------------+---------------------+
| 1 | Alice | [email protected] | 2026-01-01 10:00:00 |
| 2 | Bob | [email protected] | 2026-01-02 11:00:00 |
| 3 | Carol | [email protected] | 2026-01-03 12:00:00 |
+----+--------+-------------------+---------------------+
2. Basic SQL Queries
-- Query data (SELECT)
SELECT name, email FROM users WHERE id = 1;
-- Insert data (INSERT)
INSERT INTO users (name, email) VALUES ('Dave', '[email protected]');
-- Update data (UPDATE)
UPDATE users SET name = 'David' WHERE id = 4;
-- Delete data (DELETE)
DELETE FROM users WHERE id = 4;
3. Query Execution Process
1. Write query
SELECT * FROM users WHERE email = '[email protected]';
2. Database executes
- Table scan (check all rows)
- Find rows matching condition
- Return results
3. Problem: What if 1 million rows?
- May scan 1 million rows
- Without index: takes seconds to minutes
4. What is an Index?
Index is like a book’s table of contents that lets you jump directly to the location.
Finding without index:
- Read from first page to last
- 1000-page book → must read ~500 pages on average
Finding with index:
- Check page number in table of contents
- Jump directly to that page
- 1000-page book → find in 1 second!
SQL Index:
-- Create index
CREATE INDEX idx_email ON users(email);
-- Now searching by email is fast!
SELECT * FROM users WHERE email = '[email protected]';
-- 1 million records → 0.001 seconds
1. Why Query Optimization Matters
Impact of Slow Queries
Query execution time: 5 seconds
1 user: 5-second wait (inconvenient)
100 users: Server overload
1000 users: Server load concentration
After optimization: 0.05 seconds
→ 100x faster!
→ No problem even with 1000 concurrent users
Real Case
Before optimization:
-- Execution time: 8.5 seconds
SELECT * FROM orders
WHERE user_id = 123
AND created_at >= '2026-01-01';
After optimization:
-- Add index
CREATE INDEX idx_user_created ON orders(user_id, created_at);
-- Execution time: 0.02 seconds (425x faster!)
SELECT * FROM orders
WHERE user_id = 123
AND created_at >= '2026-01-01';
2. Index Basics
How Indexes Work
Full Table Scan (No Index)
SELECT * FROM users WHERE email = '[email protected]';
┌────┬───────┬──────────────────┐
│ id │ name │ email │
├────┼───────┼──────────────────┤
│ 1 │ Alice │ [email protected] │ ← Check
│ 2 │ Bob │ [email protected] │ ← Check
│ 3 │ Carol │ [email protected]│ ← Check
│... │ ... │ ... │ ← Check
│1M │ ... │ ... │ ← Check
Check all rows! (slow)
Index Scan (With Index)
CREATE INDEX idx_email ON users(email);
Index (B-Tree structure):
[email protected]
/ \
[email protected] [email protected]
/ \ / \
alice@... bob@... carol@... dave@...
Find quickly with binary search!
1 million records → find in ~20 comparisons
Creating Indexes
-- Single column index
CREATE INDEX idx_email ON users(email);
-- Composite index (multiple columns)
CREATE INDEX idx_user_created ON orders(user_id, created_at);
-- Unique index
CREATE UNIQUE INDEX idx_email_unique ON users(email);
-- Drop index
DROP INDEX idx_email ON users;
-- List indexes
SHOW INDEX FROM users;
Index Usage Examples
-- ✅ Index used (fast)
SELECT * FROM users WHERE email = '[email protected]';
-- ❌ Index not used (slow)
SELECT * FROM users WHERE LOWER(email) = '[email protected]';
-- Reason: Functions invalidate indexes
-- ✅ Solution: Function-based index
CREATE INDEX idx_email_lower ON users(LOWER(email));
3. Analyzing Queries with EXPLAIN
What is EXPLAIN?
EXPLAIN shows in what order the optimizer will read.
EXPLAIN SELECT * FROM users WHERE email = '[email protected]';
Reading EXPLAIN Results
MySQL Example:
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| 1 | SIMPLE | users | ALL | NULL | NULL | NULL | NULL | 1000 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
Key Column Explanations: 1) type (Access Method)
Performance order (fast → slow):
system > const > eq_ref > ref > range > index > ALL
✅ const: Find 1 row with PRIMARY KEY or UNIQUE (best)
✅ ref: Find multiple rows with index (good)
✅ range: Index range scan (okay)
❌ index: Full index scan (slow)
❌ ALL: Full table scan (very slow)
2) rows (Estimated Row Count)
rows = 1000000 ← Scan 1 million rows (slow!)
rows = 10 ← Scan only 10 rows (fast!)
3) key (Index Used)
key = NULL ← No index used (slow!)
key = idx_email ← Index used (fast!)
Practical Example
Before optimization:
EXPLAIN SELECT * FROM orders WHERE user_id = 123;
+------+-------+------+---------------+------+------+----------+
| type | table | key | possible_keys | rows | Extra |
+------+-------+------+---------------+------+------+----------+
| ALL | orders| NULL | NULL | 500K | Using where |
+------+-------+------+---------------+------+------+----------+
Problem: type = ALL (full scan), rows = 500,000
After optimization:
CREATE INDEX idx_user_id ON orders(user_id);
EXPLAIN SELECT * FROM orders WHERE user_id = 123;
+------+-------+------------+---------------+------+-------+
| type | table | key | possible_keys | rows | Extra |
+------+-------+------------+---------------+------+-------+
| ref | orders| idx_user_id| idx_user_id | 50 | |
+------+-------+------------+---------------+------+-------+
Improvement: type = ref (index used), rows = 50
4. Index Design Strategy
Composite Index Order
Important: Index column order greatly affects performance!
-- Query
SELECT * FROM orders
WHERE user_id = 123
AND status = 'completed'
AND created_at >= '2026-01-01';
-- ❌ Inefficient
CREATE INDEX idx_bad ON orders(created_at, status, user_id);
-- ✅ Efficient
CREATE INDEX idx_good ON orders(user_id, status, created_at);
Index Column Order Principles:
1. = conditions (first)
2. Range conditions (last)
3. High selectivity columns first
Example:
WHERE user_id = 123 ← = condition (1st priority)
AND status = 'completed' ← = condition (2nd priority)
AND created_at >= '2026-01-01' ← range condition (3rd priority)
Index: (user_id, status, created_at)
Index Usage Conditions
✅ Index Used:
-- 1. = condition
SELECT * FROM users WHERE id = 1;
-- 2. IN condition
SELECT * FROM users WHERE id IN (1, 2, 3);
-- 3. Range condition
SELECT * FROM users WHERE created_at >= '2026-01-01';
-- 4. LIKE (prefix matching)
SELECT * FROM users WHERE email LIKE 'alice%';
-- 5. Composite index from left
CREATE INDEX idx_abc ON table(a, b, c);
SELECT * FROM table WHERE a = 1 AND b = 2; -- Index used
❌ Index Not Used:
-- 1. Function usage
SELECT * FROM users WHERE YEAR(created_at) = 2026;
-- 2. Calculation
SELECT * FROM users WHERE id + 1 = 2;
-- 3. LIKE (middle/end matching)
SELECT * FROM users WHERE email LIKE '%@gmail.com';
-- 4. OR condition (with non-indexed column)
SELECT * FROM users WHERE id = 1 OR name = 'Alice';
-- 5. Using only middle column of composite index
CREATE INDEX idx_abc ON table(a, b, c);
SELECT * FROM table WHERE b = 2; -- Index not used!
5. Solving N+1 Problem
What is N+1 Problem?
N+1 problem is a pattern where something that can be fetched once is fetched N additional times.
// ❌ N+1 problem (101 queries)
// 1. Query user list (1 query)
const users = await db.query('SELECT * FROM users LIMIT 100');
// 2. Query post count for each user (100 queries)
for (const user of users) {
const posts = await db.query(
'SELECT COUNT(*) FROM posts WHERE user_id = ?',
[user.id]
);
user.postCount = posts[0].count;
}
// Total 101 queries! (very slow)
Solution 1: JOIN
-- ✅ Solve with 1 query
SELECT
u.id,
u.name,
COUNT(p.id) as post_count
FROM users u
LEFT JOIN posts p ON u.id = p.user_id
GROUP BY u.id, u.name
LIMIT 100;
-- 101 queries → 1 query (100x faster!)
Solution 2: IN Clause
// ✅ Solve with 2 queries
// 1. Query user list
const users = await db.query('SELECT * FROM users LIMIT 100');
const userIds = users.map(u => u.id);
// 2. Query all users' post counts at once
const postCounts = await db.query(`
SELECT user_id, COUNT(*) as count
FROM posts
WHERE user_id IN (?)
GROUP BY user_id
`, [userIds]);
// 3. Map results
const countMap = Object.fromEntries(
postCounts.map(p => [p.user_id, p.count])
);
users.forEach(user => {
user.postCount = countMap[user.id] || 0;
});
// 101 queries → 2 queries (50x faster!)
N+1 Problem in ORMs
Sequelize (Node.js)
// ❌ N+1 problem
const users = await User.findAll();
for (const user of users) {
const posts = await user.getPosts(); // N queries
}
// ✅ Eager Loading
const users = await User.findAll({
include: [{ model: Post }] // 1 query (JOIN)
});
Django (Python)
# ❌ N+1 problem
users = User.objects.all()
for user in users:
posts = user.posts.all() # N queries
# ✅ select_related (1:1, N:1)
users = User.objects.select_related('profile').all()
# ✅ prefetch_related (1:N, N:N)
users = User.objects.prefetch_related('posts').all()
6. Join Optimization
JOIN Types
-- INNER JOIN (intersection)
SELECT u.name, o.order_id
FROM users u
INNER JOIN orders o ON u.id = o.user_id;
-- LEFT JOIN (all from left table)
SELECT u.name, o.order_id
FROM users u
LEFT JOIN orders o ON u.id = o.user_id;
-- RIGHT JOIN (all from right table)
SELECT u.name, o.order_id
FROM users u
RIGHT JOIN orders o ON u.id = o.user_id;
JOIN Optimization Tips
1) Smaller Table First
-- ❌ Large table first
SELECT *
FROM orders o -- 1 million records
JOIN users u ON o.user_id = u.id -- 1000 records
WHERE u.country = 'KR';
-- ✅ Small table first
SELECT *
FROM users u -- 1000 records
JOIN orders o ON u.id = o.user_id -- 1 million records
WHERE u.country = 'KR';
2) Index on JOIN Conditions
-- Index required on JOIN condition columns
CREATE INDEX idx_user_id ON orders(user_id);
CREATE INDEX idx_id ON users(id); -- Auto-created if PRIMARY KEY
SELECT *
FROM users u
JOIN orders o ON u.id = o.user_id;
3) Filter with WHERE First
-- ❌ Filter after JOIN
SELECT *
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE o.created_at >= '2026-01-01';
-- ✅ Filter first with subquery
SELECT *
FROM users u
JOIN (
SELECT * FROM orders
WHERE created_at >= '2026-01-01'
) o ON u.id = o.user_id;
7. Subquery Optimization
Inefficient Subqueries
-- ❌ Correlated Subquery
-- Subquery executes for each row of outer query (slow!)
SELECT
u.name,
(SELECT COUNT(*) FROM posts WHERE user_id = u.id) as post_count
FROM users u;
-- If 1000 users, subquery executes 1000 times!
Optimize with JOIN
-- ✅ Use JOIN (fast)
SELECT
u.name,
COUNT(p.id) as post_count
FROM users u
LEFT JOIN posts p ON u.id = p.user_id
GROUP BY u.id, u.name;
-- Solved with 1 query!
EXISTS vs IN
-- Check existence only in large table
-- ✅ Use EXISTS (fast)
SELECT * FROM users u
WHERE EXISTS (
SELECT 1 FROM orders o
WHERE o.user_id = u.id
);
-- ❌ Use IN (slow)
SELECT * FROM users u
WHERE u.id IN (
SELECT user_id FROM orders
);
-- EXISTS stops at first match
-- IN loads all results into memory
8. Practical Optimization Cases
Case 1: Pagination
❌ Using OFFSET (slow)
-- Query 1 millionth page
SELECT * FROM posts
ORDER BY created_at DESC
LIMIT 20 OFFSET 1000000;
-- Problem: Reads all 1 million to skip them!
-- Execution time: 5 seconds
✅ Cursor-Based Pagination (fast)
-- First page
SELECT * FROM posts
ORDER BY created_at DESC
LIMIT 20;
-- Next page (using last created_at)
SELECT * FROM posts
WHERE created_at < '2026-03-31 10:00:00'
ORDER BY created_at DESC
LIMIT 20;
-- Uses index only, no skipping!
-- Execution time: 0.01 seconds (500x faster!)
Case 2: COUNT(*) Optimization
❌ Full Count (slow)
-- 1 million record table
SELECT COUNT(*) FROM posts;
-- Execution time: 2 seconds
✅ Use Approximation (fast)
-- MySQL
SELECT TABLE_ROWS
FROM information_schema.TABLES
WHERE TABLE_NAME = 'posts';
-- Execution time: 0.001 seconds
-- PostgreSQL
SELECT reltuples::bigint
FROM pg_class
WHERE relname = 'posts';
✅ Use Caching
// Cache in Redis
const cachedCount = await redis.get('posts:count');
if (cachedCount) {
return cachedCount;
}
const count = await db.query('SELECT COUNT(*) FROM posts');
await redis.setex('posts:count', 3600, count); // Cache for 1 hour
return count;
Case 3: LIKE Search Optimization
❌ Middle Matching (index not used)
SELECT * FROM users WHERE name LIKE '%alice%';
-- Full scan! (slow)
✅ Full-Text Search Index
-- MySQL
CREATE FULLTEXT INDEX idx_name_fulltext ON users(name);
SELECT * FROM users
WHERE MATCH(name) AGAINST('alice');
-- Uses full-text search index! (fast)
-- PostgreSQL
CREATE INDEX idx_name_gin ON users USING gin(to_tsvector('english', name));
SELECT * FROM users
WHERE to_tsvector('english', name) @@ to_tsquery('alice');
✅ Use Elasticsearch
// Elasticsearch recommended for large-scale text search
const result = await esClient.search({
index: 'users',
body: {
query: {
match: { name: 'alice' }
}
}
});
9. Query Optimization Checklist
Essential Checks
✅ Check execution plan with EXPLAIN
✅ Add index if type is ALL
✅ Improve WHERE conditions if rows is large
✅ Confirm indexes exist on JOIN conditions
✅ Check for N+1 problems
✅ Remove unnecessary SELECT *
✅ Use LIMIT (only what's needed)
✅ Use JOIN instead of subqueries
✅ Minimize function usage
✅ Optimize composite index order
Query Writing Principles
1) Query Only Needed Columns
-- ❌ Query all columns
SELECT * FROM users;
-- ✅ Only needed columns
SELECT id, name, email FROM users;
2) Use DISTINCT Carefully
-- ❌ DISTINCT (requires sorting, slow)
SELECT DISTINCT user_id FROM orders;
-- ✅ GROUP BY (utilizes index)
SELECT user_id FROM orders GROUP BY user_id;
3) UNION vs UNION ALL
-- ❌ UNION (removes duplicates, slow)
SELECT id FROM users
UNION
SELECT id FROM deleted_users;
-- ✅ UNION ALL (allows duplicates, fast)
SELECT id FROM users
UNION ALL
SELECT id FROM deleted_users;
10. Index Monitoring
Finding Unused Indexes
MySQL:
-- Index usage statistics
SELECT
TABLE_NAME,
INDEX_NAME,
SEQ_IN_INDEX,
COLUMN_NAME
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = 'mydb'
ORDER BY TABLE_NAME, INDEX_NAME, SEQ_IN_INDEX;
PostgreSQL:
-- Unused indexes
SELECT
schemaname,
tablename,
indexname,
idx_scan,
idx_tup_read,
idx_tup_fetch
FROM pg_stat_user_indexes
WHERE idx_scan = 0 -- Never used
ORDER BY schemaname, tablename;
Check Index Size
-- MySQL
SELECT
TABLE_NAME,
INDEX_NAME,
ROUND(STAT_VALUE * @@innodb_page_size / 1024 / 1024, 2) as size_mb
FROM mysql.innodb_index_stats
WHERE DATABASE_NAME = 'mydb'
ORDER BY size_mb DESC;
-- PostgreSQL
SELECT
tablename,
indexname,
pg_size_pretty(pg_relation_size(indexrelid)) as size
FROM pg_stat_user_indexes
ORDER BY pg_relation_size(indexrelid) DESC;
11. Practical Optimization Workflow
Step 1: Find Slow Queries
Enable MySQL Slow Query Log:
-- Enable slow query log
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1; -- Log queries over 1 second
-- Check log file location
SHOW VARIABLES LIKE 'slow_query_log_file';
Application Logging:
// Measure query execution time
const start = Date.now();
const result = await db.query('SELECT ...');
const duration = Date.now() - start;
if (duration > 1000) {
console.warn(`Slow query (${duration}ms):`, query);
}
Step 2: EXPLAIN Analysis
EXPLAIN SELECT * FROM orders
WHERE user_id = 123
AND created_at >= '2026-01-01';
-- Check type, rows, key
Step 3: Add Index
CREATE INDEX idx_user_created ON orders(user_id, created_at);
Step 4: Re-measure
EXPLAIN SELECT * FROM orders
WHERE user_id = 123
AND created_at >= '2026-01-01';
-- Verify improvement
Step 5: Production Deployment
-- Index creation may cause table lock
-- Online index creation (MySQL 5.6+)
CREATE INDEX idx_user_created ON orders(user_id, created_at) ALGORITHM=INPLACE, LOCK=NONE;
-- PostgreSQL
CREATE INDEX CONCURRENTLY idx_user_created ON orders(user_id, created_at);
12. Database-Specific Optimization
MySQL Optimization
-- 1. Query cache (MySQL 5.7 and below)
SET GLOBAL query_cache_size = 268435456; -- 256MB
-- 2. InnoDB buffer pool size
SET GLOBAL innodb_buffer_pool_size = 2147483648; -- 2GB
-- 3. Connection pool
SET GLOBAL max_connections = 200;
PostgreSQL Optimization
-- 1. Update statistics
ANALYZE users;
-- 2. VACUUM (reclaim space)
VACUUM ANALYZE users;
-- 3. Configuration optimization
-- postgresql.conf
shared_buffers = 2GB
effective_cache_size = 6GB
work_mem = 50MB
13. Monitoring Tools
Query Profiling
MySQL:
-- Enable profiling
SET profiling = 1;
-- Execute query
SELECT * FROM users WHERE email = '[email protected]';
-- Check profile
SHOW PROFILES;
SHOW PROFILE FOR QUERY 1;
PostgreSQL:
-- Execution plan + actual execution time
EXPLAIN ANALYZE
SELECT * FROM users WHERE email = '[email protected]';
APM Tools
Recommended tools:
- New Relic: Comprehensive monitoring
- Datadog: Real-time monitoring
- Percona Monitoring: MySQL specialist
- pgBadger: PostgreSQL log analysis
14. Practical Example: Dashboard Query Optimization
Before Optimization (8 seconds)
-- Query dashboard statistics
SELECT
(SELECT COUNT(*) FROM users) as total_users,
(SELECT COUNT(*) FROM orders) as total_orders,
(SELECT SUM(amount) FROM orders WHERE status = 'completed') as revenue,
(SELECT COUNT(*) FROM orders WHERE created_at >= CURDATE()) as today_orders;
-- 4 independent queries execute (slow!)
After Optimization (0.05 seconds)
-- Consolidate into 1 query
SELECT
COUNT(DISTINCT u.id) as total_users,
COUNT(o.id) as total_orders,
SUM(CASE WHEN o.status = 'completed' THEN o.amount ELSE 0 END) as revenue,
SUM(CASE WHEN o.created_at >= CURDATE() THEN 1 ELSE 0 END) as today_orders
FROM users u
LEFT JOIN orders o ON u.id = o.user_id;
-- 160x faster!
Additional Optimization: Caching
// Redis caching
async function getDashboardStats() {
const cached = await redis.get('dashboard:stats');
if (cached) {
return JSON.parse(cached);
}
const stats = await db.query('SELECT ...');
await redis.setex('dashboard:stats', 300, JSON.stringify(stats)); // 5 minutes
return stats;
}
FAQ
Q1. Are more indexes always better? No! Too many indexes:
- Slow INSERT/UPDATE/DELETE (indexes also update)
- Increased disk space usage
- Create only necessary indexes Q2. Should I create indexes for all WHERE conditions? No. Create only for high-selectivity columns.
-- ❌ Low selectivity (index ineffective)
CREATE INDEX idx_gender ON users(gender); -- Only 2 values (M/F)
-- ✅ High selectivity (index effective)
CREATE INDEX idx_email ON users(email); -- All values different
Q3. Composite index vs single indexes? Depends on query patterns.
-- Always used together: composite index
WHERE user_id = 123 AND status = 'completed'
→ CREATE INDEX idx_user_status ON orders(user_id, status);
-- Used separately: single indexes
WHERE user_id = 123 (query 1)
WHERE status = 'completed' (query 2)
→ CREATE INDEX idx_user_id ON orders(user_id);
→ CREATE INDEX idx_status ON orders(status);
Q4. Still slow after adding index
-- Check with EXPLAIN
EXPLAIN SELECT ...;
-- Reasons index not used:
-- 1. Function usage: WHERE YEAR(created_at) = 2026
-- 2. Type mismatch: WHERE id = '123' (id is INT but string used)
-- 3. OR condition: WHERE a = 1 OR b = 2
-- 4. Composite index order mismatch
Summary
Key Points
Indexes:
- Like a book’s table of contents
- Create on WHERE, JOIN, ORDER BY columns
- Composite index order matters EXPLAIN:
- Check query execution plan
- Optimize if type = ALL
- Add index if rows is large N+1 Problem:
- Executing N+1 queries when 1 is possible
- Solve with JOIN or IN Optimization Order:
- Find slow queries
- Analyze with EXPLAIN
- Add indexes
- Re-measure
Performance Improvement Effects
Before → After optimization
Full Scan: 5s → 0.01s (500x)
N+1 Problem: 10s → 0.1s (100x)
Composite Index: 2s → 0.02s (100x)
Pagination: 3s → 0.01s (300x)
Recommended Next Articles
Keywords: SQL, query-optimization, Database, Index, EXPLAIN, Performance, MySQL, PostgreSQL, N+1, JOIN
Related Articles (Internal Links)
Other articles related to this topic.
- MongoDB 고급 가이드 | 인덱싱·Aggregation·샤딩·복제·성능 최적화
- C++ 쿼리 최적화 완벽 가이드 | 인덱스 선택·실행 계획·통계·비용 모델·프로덕션 패턴 [#49-3]
- Python 데이터베이스 | SQLite, PostgreSQL, ORM 완벽 정리
- MySQL EXPLAIN으로 느린 쿼리 잡기 | 실행 계획·인덱스 튜닝 실전
- C++ 데이터베이스 쿼리 최적화 완벽 가이드 | 인덱스·실행 계획·캐싱·N+1 해결 [#51-8]
Keywords Covered in This Article (Related Search Terms)
This article covers SQL, query-optimization, Database, Index, EXPLAIN, Performance, MySQL, PostgreSQL.