๋ฐ์ดํฐ๋ฒ ์ด์ค ์ธ๋ฑ์ค์ ์ฟผ๋ฆฌ ์ต์ ํ๋ ๋ฌด์์ธ๊ฐ
๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์ฌ์ฉํ๋ค ๋ณด๋ฉด ์ ์ ๋๋ ค์ง๋ ๊ฒฝํ์ ํ๊ฒ ๋๋ค.
์ฒ์์ ๋น ๋ฅด๋ ์กฐํ๊ฐ ๋ฐ์ดํฐ๊ฐ ์์ด๋ฉด์
10์ด, 30์ด, ์ฌ์ง์ด 1๋ถ ์ด์ ๊ฑธ๋ฆฌ๋ ์ํฉ์ด ๋ฐ์ํ๋ค.
์ด๋ ๋ง์น ์ฑ
์ด ์ ๋ฆฌ๋์ง ์์ ๋์๊ด๊ณผ ๊ฐ๋ค.
์ฑ
์ด 100๊ถ์ผ ๋๋ ํ๋์ฉ ์ฐพ์๋ ๊ด์ฐฎ์ง๋ง,
10๋ง ๊ถ์ด ๋๋ฉด ์ํ๋ ์ฑ
์ ์ฐพ๊ธฐ ์ํด
๋ชจ๋ ์ฑ
์ ์ผ์ผ์ด ํ์ธํด์ผ ํ๋ค๋ฉด ์์ฒญ๋ ์๊ฐ์ด ๊ฑธ๋ฆฐ๋ค.
๋ฐ์ดํฐ๋ฒ ์ด์ค ์ธ๋ฑ์ค๋ ๋ฐ๋ก ์ด๋ฐ ์ํฉ์์
์ฑ
์ ๋ชฉ์ฐจ๋ ์์ธ์ฒ๋ผ ๋น ๋ฅด๊ฒ ์ํ๋ ๋ฐ์ดํฐ๋ฅผ ์ฐพ์ ์ ์๊ฒ ํด์ฃผ๋
ํต์ฌ ๊ธฐ์ ์ด๋ค.
์ ์ธ๋ฑ์ค์ ์ฟผ๋ฆฌ ์ต์ ํ๊ฐ ํ์ํ ๊น?
์ต์ ํ๋์ง ์์ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ ๋ค์๊ณผ ๊ฐ์ ๋ฌธ์ ๋ฅผ ์ผ์ผํจ๋ค.
๋ฌธ์ 1: ๋๋ฆฐ ์กฐํ ์๋
์ธ๋ฑ์ค ์์ด 100๋ง ๊ฑด์ ๋ฐ์ดํฐ์์ ํน์ ์ฌ์ฉ์๋ฅผ ์ฐพ์ผ๋ ค๋ฉด
๋ชจ๋ ํ์ ํ๋์ฉ ํ์ธํด์ผ ํ๋ค. (Full Table Scan)
๋ฌธ์ 2: ์๋ฒ ๋ฆฌ์์ค ๋ญ๋น
๋นํจ์จ์ ์ธ ์ฟผ๋ฆฌ๋ CPU์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ๊ณผ๋ํ๊ฒ ์ฌ์ฉํ๋ค.
ํ๋์ ๋๋ฆฐ ์ฟผ๋ฆฌ๊ฐ ์ ์ฒด ์๋ฒ๋ฅผ ๋๋ฆฌ๊ฒ ๋ง๋ ๋ค.
๋ฌธ์ 3: ๋์ ์ฒ๋ฆฌ ๋ฅ๋ ฅ ์ ํ
์ฟผ๋ฆฌ ํ๋๊ฐ ์ค๋ ๊ฑธ๋ฆฌ๋ฉด ๋ค๋ฅธ ์์ฒญ๋ค๋ ๋๊ธฐํด์ผ ํ๋ค.
๋ฝ(Lock)์ด ๊ธธ์ด์ง๋ฉด์ ๋์์ฑ์ด ๋จ์ด์ง๋ค.
๋ฌธ์ 4: ์ฌ์ฉ์ ์ดํ
ํ์ด์ง ๋ก๋ฉ์ด 3์ด ์ด์ ๊ฑธ๋ฆฌ๋ฉด ์ฌ์ฉ์ ์ดํ๋ฅ ์ด ๊ธ์ฆํ๋ค.
๊ธฐ๋ณธ ๊ฐ๋ ์์ฝ
๐ท๏ธ ์ธ๋ฑ์ค(Index)๋?
๊ฐ๋
: ๋ฐ์ดํฐ๋ฅผ ๋น ๋ฅด๊ฒ ์ฐพ๊ธฐ ์ํ ์ ๋ ฌ๋ ์๋ฃ๊ตฌ์กฐ
์ฑ
์ ์์ธ ๋น์ :
์ฑ
๋ค์ชฝ์ ์์ธ์ ๋ณด๋ฉด โ๋ฐ์ดํฐ๋ฒ ์ด์ค - 45์ชฝ, 123์ชฝโ์ฒ๋ผ
๋จ์ด์ ํ์ด์ง ๋ฒํธ๊ฐ ์ ๋ ฌ๋์ด ์๋ค.
์์ธ์ด ์๋ค๋ฉด ์ฒ์๋ถํฐ ๋๊น์ง ๋ชจ๋ ํ์ด์ง๋ฅผ ๋ค์ ธ์ผ ํ์ง๋ง,
์์ธ์ด ์์ผ๋ฉด ์ํ๋ ํ์ด์ง๋ก ๋ฐ๋ก ๊ฐ ์ ์๋ค.
์ธ๋ฑ์ค์ ์ฅ๋จ์
์ฅ์ :
- ์กฐํ ์๋ ๋ํญ ํฅ์: O(N) โ O(log N)
- ์ ๋ ฌ๊ณผ ๊ทธ๋ฃนํ ์ฑ๋ฅ ํฅ์
- WHERE, JOIN ์กฐ๊ฑด ๋น ๋ฅธ ์ฒ๋ฆฌ
๋จ์ :
- ์ถ๊ฐ ์ ์ฅ ๊ณต๊ฐ ํ์: ํ ์ด๋ธ ํฌ๊ธฐ์ 10-20%
- INSERT/UPDATE/DELETE ๋๋ ค์ง: ์ธ๋ฑ์ค๋ ํจ๊ป ๊ฐฑ์
- ์๋ชป ์ค๊ณํ๋ฉด ์คํ๋ ค ์ฑ๋ฅ ์ ํ
๐ท๏ธ ์ธ๋ฑ์ค์ ์ข ๋ฅ
1. B-Tree ์ธ๋ฑ์ค (๊ฐ์ฅ ์ผ๋ฐ์ )
ํน์ง: ๊ท ํ ์กํ ํธ๋ฆฌ ๊ตฌ์กฐ๋ก ์ ๋ ฌ๋ ์ํ ์ ์ง
์ฌ์ฉ: ๋๋ถ๋ถ์ ์กฐํ, ์ ๋ ฌ, ๋ฒ์ ๊ฒ์
์๊ฐ ๋ณต์ก๋: O(log N)
์ ํฉํ ๊ฒฝ์ฐ:
- WHERE id = 100
- WHERE age BETWEEN 20 AND 30
- ORDER BY created_at
๋ถ์ ํฉํ ๊ฒฝ์ฐ:
- WHERE name LIKE โ%๊น%โ (์์ ์์ผ๋์นด๋)
- ์นด๋๋๋ฆฌํฐ๊ฐ ๋งค์ฐ ๋ฎ์ ์ปฌ๋ผ (์ฑ๋ณ, ํ์ ๋ฑ)
2. Hash ์ธ๋ฑ์ค
ํน์ง: ํด์ ํจ์๋ก ๋น ๋ฅธ ๊ฒ์ (๋ฉ๋ชจ๋ฆฌ ๊ธฐ๋ฐ)
์ฌ์ฉ: ๋๋ฑ ๋น๊ต (=)
์๊ฐ ๋ณต์ก๋: O(1)
์ ํฉํ ๊ฒฝ์ฐ: WHERE id = 100
๋ถ์ ํฉํ ๊ฒฝ์ฐ: ๋ฒ์ ๊ฒ์, ์ ๋ ฌ (BETWEEN, ORDER BY ๋ถ๊ฐ)
3. Full-Text ์ธ๋ฑ์ค
ํน์ง: ํ
์คํธ ์ ๋ฌธ ๊ฒ์์ ํนํ
์ฌ์ฉ: ๊ธด ํ
์คํธ ๊ฒ์
์ ํฉํ ๊ฒฝ์ฐ: ๊ฒ์๊ธ ๋ด์ฉ ๊ฒ์, ํค์๋ ๊ฒ์
4. ๋ณตํฉ ์ธ๋ฑ์ค (Composite Index)
ํน์ง: ์ฌ๋ฌ ์ปฌ๋ผ์ ์กฐํฉํ ์ธ๋ฑ์ค
CREATE INDEX idx_user_name_age ON users (name, age);
์ค์ ์์น: ์ปฌ๋ผ ์์๊ฐ ๋งค์ฐ ์ค์!
ํ์ฉ ๊ฐ๋ฅ:
- WHERE name = โ๊น์ฒ ์โ
- WHERE name = โ๊น์ฒ ์โ AND age = 25
ํ์ฉ ๋ถ๊ฐ:
- WHERE age = 25 (์ฒซ ๋ฒ์งธ ์ปฌ๋ผ ๋๋ฝ)
๐ท๏ธ ์ฟผ๋ฆฌ ์ต์ ํ ํต์ฌ ๊ฐ๋
1. ์คํ ๊ณํ (Execution Plan)
๋ฐ์ดํฐ๋ฒ ์ด์ค๊ฐ ์ฟผ๋ฆฌ๋ฅผ ์ด๋ป๊ฒ ์คํํ ์ง ๊ณํํ ๊ฒ
ํ์ธ ๋ฐฉ๋ฒ: EXPLAIN ๋ช
๋ น์ด ์ฌ์ฉ
2. ์นด๋๋๋ฆฌํฐ (Cardinality)
์ปฌ๋ผ์ ๊ณ ์ ํ ๊ฐ์ ๊ฐ์
- ๋์ ์นด๋๋๋ฆฌํฐ: ์ด๋ฉ์ผ, ์ฃผ๋ฏผ๋ฒํธ (์ธ๋ฑ์ค ํจ๊ณผ โ)
- ๋ฎ์ ์นด๋๋๋ฆฌํฐ: ์ฑ๋ณ, ์ํ์ฝ๋ (์ธ๋ฑ์ค ํจ๊ณผ โ)
3. ์ ํ๋ (Selectivity)
์ ์ฒด ๋ฐ์ดํฐ ์ค ์กฐ๊ฑด์ ๋ง๋ ๋น์จ
- ๋ฎ์ ์ ํ๋: ์ ์ฒด์ 5% ์ ํ (์ธ๋ฑ์ค ํจ๊ณผ โ)
- ๋์ ์ ํ๋: ์ ์ฒด์ 80% ์ ํ (Full Scan์ด ๋์ ์๋)
4. ์ปค๋ฒ๋ง ์ธ๋ฑ์ค (Covering Index)
์ฟผ๋ฆฌ์ ํ์ํ ๋ชจ๋ ์ปฌ๋ผ์ด ์ธ๋ฑ์ค์ ํฌํจ๋์ด
ํ
์ด๋ธ์ ์กฐํํ์ง ์์๋ ๋๋ ์ํ
-- ์ธ๋ฑ์ค: (name, age, email)
SELECT name, age, email FROM users WHERE name = '๊น์ฒ ์';
-- ํ
์ด๋ธ ์ ๊ทผ ์์ด ์ธ๋ฑ์ค๋ง์ผ๋ก ์กฐํ ์๋ฃ!
์ธ๋ฑ์ค ์์ฑ ์์
MySQL ์ธ๋ฑ์ค ์์ฑ
๋จ์ผ ์ปฌ๋ผ ์ธ๋ฑ์ค
-- ๊ธฐ๋ณธ ์ธ๋ฑ์ค ์์ฑ
CREATE INDEX idx_email ON users (email);
-- ์ ๋ํฌ ์ธ๋ฑ์ค
CREATE UNIQUE INDEX idx_unique_email ON users (email);
-- ์ธ๋ฑ์ค ์ญ์
DROP INDEX idx_email ON users;
-- ์ธ๋ฑ์ค ๋ชฉ๋ก ํ์ธ
SHOW INDEX FROM users;
๋ณตํฉ ์ธ๋ฑ์ค (์ปฌ๋ผ ์์ ์ค์!)
-- ์ด๋ฆ + ๋์ด ๋ณตํฉ ์ธ๋ฑ์ค
CREATE INDEX idx_name_age ON users (name, age);
-- ํ์ฉ ๊ฐ๋ฅํ ์ฟผ๋ฆฌ
SELECT * FROM users WHERE name = '๊น์ฒ ์';
SELECT * FROM users WHERE name = '๊น์ฒ ์' AND age = 25;
SELECT * FROM users WHERE name = '๊น์ฒ ์' AND age > 20;
-- ํ์ฉ ๋ถ๊ฐ (์ฒซ ๋ฒ์งธ ์ปฌ๋ผ ๋๋ฝ)
SELECT * FROM users WHERE age = 25;
์ธ๋ฑ์ค ํํธ
-- ํน์ ์ธ๋ฑ์ค ๊ฐ์ ์ฌ์ฉ
SELECT * FROM users USE INDEX (idx_name_age)
WHERE name = '๊น์ฒ ์';
-- ์ธ๋ฑ์ค ๋ฌด์
SELECT * FROM users IGNORE INDEX (idx_email)
WHERE email = 'test@example.com';
PostgreSQL ์ธ๋ฑ์ค ์์ฑ
๊ธฐ๋ณธ ์ธ๋ฑ์ค
-- B-Tree ์ธ๋ฑ์ค (๊ธฐ๋ณธ)
CREATE INDEX idx_email ON users (email);
-- ๋ถ๋ถ ์ธ๋ฑ์ค (์กฐ๊ฑด๋ถ)
CREATE INDEX idx_active_users ON users (email)
WHERE status = 'active';
-- ํํ์ ์ธ๋ฑ์ค
CREATE INDEX idx_lower_email ON users (LOWER(email));
๊ณ ๊ธ ์ธ๋ฑ์ค
-- GIN ์ธ๋ฑ์ค (๋ฐฐ์ด, JSONB ๊ฒ์)
CREATE INDEX idx_tags ON posts USING GIN (tags);
-- GiST ์ธ๋ฑ์ค (์ง๋ฆฌ ์ ๋ณด, ๋ฒ์)
CREATE INDEX idx_location ON stores USING GiST (location);
-- BRIN ์ธ๋ฑ์ค (๋์ฉ๋ ์๊ณ์ด ๋ฐ์ดํฐ)
CREATE INDEX idx_created_at ON logs USING BRIN (created_at);
์ฟผ๋ฆฌ ์ต์ ํ ์ค์ ์์
EXPLAIN์ผ๋ก ์คํ ๊ณํ ๋ถ์
MySQL EXPLAIN
EXPLAIN SELECT * FROM users WHERE email = 'test@example.com';
์ฃผ์ ํ์ธ ํญ๋ชฉ:
| ํญ๋ชฉ | ์๋ฏธ | ์ข์ ๊ฐ |
|---|---|---|
| type | ์กฐ์ธ ํ์ | const, eq_ref |
| possible_keys | ์ฌ์ฉ ๊ฐ๋ฅํ ์ธ๋ฑ์ค | - |
| key | ์ค์ ์ฌ์ฉ๋ ์ธ๋ฑ์ค | NULL์ด ์๋ |
| rows | ์์ ๊ฒ์ ํ ์ | ์ ์์๋ก ์ข์ |
| Extra | ์ถ๊ฐ ์ ๋ณด | Using index |
type ๊ฐ ํด์ (์ข์ ์์):
- const: PRIMARY KEY๋ UNIQUE ์ธ๋ฑ์ค๋ก ๋จ์ผ ํ ์กฐํ (์ต๊ณ )
- eq_ref: ์กฐ์ธ ์ PRIMARY KEY ์ฌ์ฉ
- ref: ์ผ๋ฐ ์ธ๋ฑ์ค ์ฌ์ฉ
- range: ๋ฒ์ ๊ฒ์ (BETWEEN, >, <)
- index: ์ธ๋ฑ์ค ํ ์ค์บ
- ALL: ํ ์ด๋ธ ํ ์ค์บ (์ต์ )
PostgreSQL EXPLAIN ANALYZE
EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'test@example.com';
์ถ๋ ฅ ์์:
Index Scan using idx_email on users (cost=0.42..8.44 rows=1 width=100)
Index Cond: (email = 'test@example.com'::text)
Planning Time: 0.123 ms
Execution Time: 0.045 ms
์ฃผ์ ํ์ธ ํญ๋ชฉ:
- Seq Scan: ํ ์ด๋ธ ํ ์ค์บ (๋์จ)
- Index Scan: ์ธ๋ฑ์ค ์ฌ์ฉ (์ข์)
- Index Only Scan: ์ปค๋ฒ๋ง ์ธ๋ฑ์ค (์ต๊ณ )
- rows: ์์ ํ ์
- cost: ์์ ๋น์ฉ
๋๋ฆฐ ์ฟผ๋ฆฌ ์ต์ ํ ์์
Before: ์ต์ ํ ์ (๋๋ฆผ)
-- ์ธ๋ฑ์ค๊ฐ ์๋ ์ํ
SELECT * FROM orders
WHERE customer_id = 12345
AND order_date >= '2024-01-01'
AND status = 'completed';
-- EXPLAIN ๊ฒฐ๊ณผ: type = ALL (ํ
์ด๋ธ ํ ์ค์บ)
-- ์คํ ์๊ฐ: 5.2์ด (100๋ง ๊ฑด ๊ธฐ์ค)
After: ์ต์ ํ ํ (๋น ๋ฆ)
-- ๋ณตํฉ ์ธ๋ฑ์ค ์์ฑ
CREATE INDEX idx_orders_optimization
ON orders (customer_id, order_date, status);
-- ๊ฐ์ ์ฟผ๋ฆฌ ์คํ
SELECT * FROM orders
WHERE customer_id = 12345
AND order_date >= '2024-01-01'
AND status = 'completed';
-- EXPLAIN ๊ฒฐ๊ณผ: type = ref (์ธ๋ฑ์ค ์ฌ์ฉ)
-- ์คํ ์๊ฐ: 0.05์ด (104๋ฐฐ ๋น ๋ฆ!)
์ปค๋ฒ๋ง ์ธ๋ฑ์ค๋ก ์ถ๊ฐ ์ต์ ํ
-- ํ์ํ ์ปฌ๋ผ๋ง ์กฐํ
SELECT order_id, customer_id, order_date, status, total_amount
FROM orders
WHERE customer_id = 12345
AND order_date >= '2024-01-01'
AND status = 'completed';
-- ์ปค๋ฒ๋ง ์ธ๋ฑ์ค ์์ฑ
CREATE INDEX idx_orders_covering
ON orders (customer_id, order_date, status, order_id, total_amount);
-- EXPLAIN ๊ฒฐ๊ณผ: Extra = Using index (ํ
์ด๋ธ ์ ๊ทผ ์์)
-- ์คํ ์๊ฐ: 0.02์ด (์ถ๊ฐ๋ก 2.5๋ฐฐ ๋น ๋ฆ!)
N+1 ๋ฌธ์ ํด๊ฒฐ
Before: N+1 ๋ฌธ์ ๋ฐ์
# ๊ฒ์๊ธ ๋ชฉ๋ก ์กฐํ (1๋ฒ)
posts = db.query("SELECT * FROM posts LIMIT 10")
# ๊ฐ ๊ฒ์๊ธ์ ์์ฑ์ ์กฐํ (N๋ฒ)
for post in posts:
author = db.query(f"SELECT * FROM users WHERE id = {post.author_id}")
post.author_name = author.name
# ์ด ์ฟผ๋ฆฌ ์: 1 + 10 = 11๋ฒ
After: JOIN์ผ๋ก ํด๊ฒฐ
-- ํ ๋ฒ์ ์ฟผ๋ฆฌ๋ก ํด๊ฒฐ
SELECT
p.*,
u.name as author_name,
u.email as author_email
FROM posts p
INNER JOIN users u ON p.author_id = u.id
LIMIT 10;
-- ์ด ์ฟผ๋ฆฌ ์: 1๋ฒ (11๋ฐฐ ๊ฐ์!)
๋์: IN ์ ์ฌ์ฉ
# ๊ฒ์๊ธ ๋ชฉ๋ก ์กฐํ (1๋ฒ)
posts = db.query("SELECT * FROM posts LIMIT 10")
author_ids = [post.author_id for post in posts]
# ์์ฑ์ ์ผ๊ด ์กฐํ (1๋ฒ)
authors = db.query(f"SELECT * FROM users WHERE id IN ({','.join(map(str, author_ids))})")
author_map = {author.id: author for author in authors}
# ๋ฉ๋ชจ๋ฆฌ์์ ๋งคํ
for post in posts:
post.author = author_map[post.author_id]
# ์ด ์ฟผ๋ฆฌ ์: 2๋ฒ (5.5๋ฐฐ ๊ฐ์)
ํ์ด์ง ์ต์ ํ
Before: OFFSET ์ฌ์ฉ (๋๋ฆผ)
-- 10๋ง ๋ฒ์งธ ํ์ด์ง ์กฐํ (๋งค์ฐ ๋๋ฆผ)
SELECT * FROM posts
ORDER BY id
LIMIT 20 OFFSET 100000;
-- ๋ฌธ์ : 10๋ง ๊ฑด์ ์ฝ๊ณ ๋ฒ๋ ค์ผ ํจ
-- ์คํ ์๊ฐ: 2.3์ด
After: Cursor ๊ธฐ๋ฐ ํ์ด์ง (๋น ๋ฆ)
-- ์ฒซ ํ์ด์ง
SELECT * FROM posts
ORDER BY id
LIMIT 20;
-- ๋ง์ง๋ง id: 20
-- ๋ค์ ํ์ด์ง (WHERE ์กฐ๊ฑด ์ฌ์ฉ)
SELECT * FROM posts
WHERE id > 20
ORDER BY id
LIMIT 20;
-- ์คํ ์๊ฐ: 0.01์ด (230๋ฐฐ ๋น ๋ฆ!)
์ฟผ๋ฆฌ ์ต์ ํ ์ํฐํจํด๊ณผ ํด๊ฒฐ์ฑ
์ํฐํจํด 1: SELECT *
๋ฌธ์
-- ํ์ ์๋ ์ปฌ๋ผ๊น์ง ๋ชจ๋ ์กฐํ
SELECT * FROM users WHERE id = 100;
ํด๊ฒฐ
-- ํ์ํ ์ปฌ๋ผ๋ง ์กฐํ
SELECT id, name, email FROM users WHERE id = 100;
์ด์ : ๋คํธ์ํฌ ์ ์ก๋ ๊ฐ์, ์ปค๋ฒ๋ง ์ธ๋ฑ์ค ํ์ฉ ๊ฐ๋ฅ
์ํฐํจํด 2: ํจ์ ์ฌ์ฉ์ผ๋ก ์ธ๋ฑ์ค ๋ฌดํจํ
๋ฌธ์
-- ์ธ๋ฑ์ค๋ฅผ ์ฌ์ฉํ ์ ์์
SELECT * FROM users WHERE YEAR(created_at) = 2024;
SELECT * FROM users WHERE LOWER(email) = 'test@example.com';
ํด๊ฒฐ
-- ๋ฒ์ ๊ฒ์์ผ๋ก ๋ณ๊ฒฝ (์ธ๋ฑ์ค ํ์ฉ)
SELECT * FROM users
WHERE created_at >= '2024-01-01'
AND created_at < '2025-01-01';
-- ๋ฐ์ดํฐ๋ฅผ ์๋ฌธ์๋ก ์ ์ฅํ๊ฑฐ๋ ํจ์ ์ธ๋ฑ์ค ์์ฑ
CREATE INDEX idx_lower_email ON users (LOWER(email));
์ํฐํจํด 3: OR ์กฐ๊ฑด ๋จ์ฉ
๋ฌธ์
-- ์ธ๋ฑ์ค ํ์ฉ ์ด๋ ค์
SELECT * FROM users
WHERE name = '๊น์ฒ ์' OR email = 'kim@example.com';
ํด๊ฒฐ
-- UNION์ผ๋ก ๋ถ๋ฆฌ (๊ฐ๊ฐ ์ธ๋ฑ์ค ์ฌ์ฉ)
SELECT * FROM users WHERE name = '๊น์ฒ ์'
UNION
SELECT * FROM users WHERE email = 'kim@example.com';
์ํฐํจํด 4: ์์ผ๋์นด๋ ์์ชฝ ์ฌ์ฉ
๋ฌธ์
-- ์ธ๋ฑ์ค๋ฅผ ์ฌ์ฉํ ์ ์์
SELECT * FROM users WHERE name LIKE '%์ฒ ์%';
ํด๊ฒฐ
-- ์์ผ๋์นด๋๋ฅผ ๋ค์ชฝ์๋ง ์ฌ์ฉ
SELECT * FROM users WHERE name LIKE '๊น์ฒ ์%';
-- ๋๋ Full-Text ์ธ๋ฑ์ค ์ฌ์ฉ
CREATE FULLTEXT INDEX idx_name_fulltext ON users (name);
SELECT * FROM users WHERE MATCH(name) AGAINST('์ฒ ์');
์ํฐํจํด 5: ์๋ธ์ฟผ๋ฆฌ ๋จ์ฉ
๋ฌธ์
-- ์๋ธ์ฟผ๋ฆฌ๊ฐ ๋งค ํ๋ง๋ค ์คํ๋จ
SELECT *,
(SELECT COUNT(*) FROM orders WHERE customer_id = u.id) as order_count
FROM users u;
ํด๊ฒฐ
-- JOIN์ผ๋ก ๋ณ๊ฒฝ
SELECT u.*, COUNT(o.id) as order_count
FROM users u
LEFT JOIN orders o ON u.id = o.customer_id
GROUP BY u.id;
์ค์ ๋ชจ๋ํฐ๋ง๊ณผ ๋ถ์
๋๋ฆฐ ์ฟผ๋ฆฌ ๋ก๊ทธ ํ์ฑํ
MySQL
-- ๋๋ฆฐ ์ฟผ๋ฆฌ ๋ก๊ทธ ํ์ฑํ
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1; -- 1์ด ์ด์ ๊ฑธ๋ฆฌ๋ ์ฟผ๋ฆฌ ๊ธฐ๋ก
-- ๋ก๊ทธ ํ์ผ ์์น ํ์ธ
SHOW VARIABLES LIKE 'slow_query_log_file';
PostgreSQL
-- postgresql.conf ์ค์
-- log_min_duration_statement = 1000 (1์ด)
-- ์คํ ์๊ฐ์ด ๊ธด ์ฟผ๋ฆฌ ํ์ธ
SELECT query, calls, total_time, mean_time
FROM pg_stat_statements
ORDER BY total_time DESC
LIMIT 10;
์ธ๋ฑ์ค ์ฌ์ฉ๋ฅ ํ์ธ
MySQL
-- ์ฌ์ฉ๋์ง ์๋ ์ธ๋ฑ์ค ์ฐพ๊ธฐ
SELECT
t.TABLE_SCHEMA,
t.TABLE_NAME,
s.INDEX_NAME,
s.COLUMN_NAME
FROM information_schema.TABLES t
INNER JOIN information_schema.STATISTICS s
ON t.TABLE_SCHEMA = s.TABLE_SCHEMA
AND t.TABLE_NAME = s.TABLE_NAME
LEFT JOIN performance_schema.table_io_waits_summary_by_index_usage i
ON i.OBJECT_SCHEMA = s.TABLE_SCHEMA
AND i.OBJECT_NAME = s.TABLE_NAME
AND i.INDEX_NAME = s.INDEX_NAME
WHERE t.TABLE_SCHEMA NOT IN ('mysql', 'information_schema', 'performance_schema')
AND i.INDEX_NAME IS NULL;
PostgreSQL
-- ์ธ๋ฑ์ค ์ฌ์ฉ ํต๊ณ
SELECT
schemaname,
tablename,
indexname,
idx_scan,
idx_tup_read,
idx_tup_fetch
FROM pg_stat_user_indexes
ORDER BY idx_scan;
์ค์ ์ต์ ํ ์ฒดํฌ๋ฆฌ์คํธ
โ ์ธ๋ฑ์ค ์ค๊ณ
- WHERE ์ ์ ์์ฃผ ์ฌ์ฉ๋๋ ์ปฌ๋ผ์ ์ธ๋ฑ์ค ์์ฑ
- JOIN ์กฐ๊ฑด ์ปฌ๋ผ์ ์ธ๋ฑ์ค ์์ฑ
- ORDER BY ์ปฌ๋ผ์ ์ธ๋ฑ์ค ๊ณ ๋ ค
- ๋ณตํฉ ์ธ๋ฑ์ค ์ปฌ๋ผ ์์ ์ต์ ํ (์นด๋๋๋ฆฌํฐ ๋์ ์)
- ์ปค๋ฒ๋ง ์ธ๋ฑ์ค ํ์ฉ
- ์ฌ์ฉํ์ง ์๋ ์ธ๋ฑ์ค ์ ๊ฑฐ
โ ์ฟผ๋ฆฌ ์์ฑ
- SELECT * ๋์ ํ์ํ ์ปฌ๋ผ๋ง ์กฐํ
- N+1 ๋ฌธ์ ํด๊ฒฐ (JOIN ๋๋ IN ์ฌ์ฉ)
- WHERE ์ ์์ ํจ์ ์ฌ์ฉ ์ง์
- OR ๋์ UNION ๊ณ ๋ ค
- LIKE โ%keyword%โ ์ง์
- ์๋ธ์ฟผ๋ฆฌ ๋์ JOIN ๊ณ ๋ ค
โ ์ฑ๋ฅ ๋ถ์
- EXPLAIN์ผ๋ก ์คํ ๊ณํ ํ์ธ
- ๋๋ฆฐ ์ฟผ๋ฆฌ ๋ก๊ทธ ๋ชจ๋ํฐ๋ง
- ์ธ๋ฑ์ค ์ฌ์ฉ๋ฅ ํ์ธ
- ์ฟผ๋ฆฌ ์คํ ์๊ฐ ์ธก์
โ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ค์
- ์ปค๋ฅ์ ํ ํฌ๊ธฐ ์ต์ ํ
- ์ฟผ๋ฆฌ ์บ์ ํ์ฉ (MySQL 5.7 ์ดํ)
- ๋ฒํผ ํ ํฌ๊ธฐ ์กฐ์
- ํต๊ณ ์ ๋ณด ์ฃผ๊ธฐ์ ๊ฐฑ์
์์ฝ
๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฑ๋ฅ์ 90%๋ ์ธ๋ฑ์ค์ ์ฟผ๋ฆฌ ์ต์ ํ์์ ๊ฒฐ์ ๋๋ค.
๐ ํต์ฌ ํฌ์ธํธ:
- ์ธ๋ฑ์ค๋ ์ฑ ์ ์์ธ๊ณผ ๊ฐ๋ค - ๋น ๋ฅธ ๊ฒ์์ ํต์ฌ
- B-Tree ์ธ๋ฑ์ค๊ฐ ๊ฐ์ฅ ์ผ๋ฐ์ ์ด๊ณ ํจ๊ณผ์
- ๋ณตํฉ ์ธ๋ฑ์ค๋ ์ปฌ๋ผ ์์๊ฐ ๋งค์ฐ ์ค์
- EXPLAIN์ผ๋ก ์คํ ๊ณํ ๋ฐ๋์ ํ์ธ
- ์ปค๋ฒ๋ง ์ธ๋ฑ์ค๋ก ํ ์ด๋ธ ์ ๊ทผ ์ ๊ฑฐ
- N+1 ๋ฌธ์ ๋ JOIN์ด๋ IN์ผ๋ก ํด๊ฒฐ
๐ ์ต์ ํ ์ฐ์ ์์:
1๋จ๊ณ: EXPLAIN์ผ๋ก ๋๋ฆฐ ์ฟผ๋ฆฌ ์ฐพ๊ธฐ
2๋จ๊ณ: WHERE/JOIN ์ปฌ๋ผ์ ์ธ๋ฑ์ค ์์ฑ
3๋จ๊ณ: ๋ณตํฉ ์ธ๋ฑ์ค๋ก ์ปค๋ฒ๋ง ์ธ๋ฑ์ค ๊ตฌ์ฑ
4๋จ๊ณ: N+1 ๋ฌธ์ ์ ๊ฑฐ
5๋จ๊ณ: ์ฟผ๋ฆฌ ๋ฆฌํฉํ ๋ง (ํจ์ ์ ๊ฑฐ, OR ์ ๊ฑฐ)
โ ๏ธ ์ฃผ์์ฌํญ:
- ์ธ๋ฑ์ค๋ ๋ง๋ฅ์ด ์๋๋ค (์ฐ๊ธฐ ์ฑ๋ฅ ์ ํ)
- ๋ชจ๋ ์ปฌ๋ผ์ ์ธ๋ฑ์ค๋ฅผ ๋ง๋ค์ง ๋ง ๊ฒ
- ์ ๊ธฐ์ ์ผ๋ก ์ฌ์ฉํ์ง ์๋ ์ธ๋ฑ์ค ์ ๊ฑฐ
- ํต๊ณ ์ ๋ณด๋ฅผ ์ฃผ๊ธฐ์ ์ผ๋ก ๊ฐฑ์
๐ ์ฑ๊ณผ ์ธก์ :
์ข์ ์ต์ ํ๋ ์ซ์๋ก ์ฆ๋ช
๋๋ค.
Before/After ์คํ ์๊ฐ์ ๋ฐ๋์ ์ธก์ ํ๊ณ
10๋ฐฐ ์ด์์ ์ฑ๋ฅ ํฅ์์ ๋ชฉํ๋ก ํ์.
๋ฐ์ดํฐ๋ฒ ์ด์ค ์ต์ ํ๋ ํ ๋ฒ์ ๋๋๋ ์์
์ด ์๋๋ค.
๋ฐ์ดํฐ๊ฐ ์์ด๊ณ ํธ๋ํฝ์ด ์ฆ๊ฐํ๋ฉด์
๊ณ์ํด์ ๋ชจ๋ํฐ๋งํ๊ณ ๊ฐ์ ํด์ผ ํ๋ ์ง์์ ์ธ ๊ณผ์ ์ด๋ค.