KR_PostgreSQL - somaz94/python-study GitHub Wiki
PostgreSQL์ ๊ฐ๋ ฅํ ์คํ์์ค ๊ฐ์ฒด-๊ด๊ณํ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์์คํ
์ด๋ค.
import psycopg2
from psycopg2.extras import DictCursor
# ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ
def get_connection():
return psycopg2.connect(
dbname="mydatabase",
user="username",
password="password",
host="localhost",
port="5432"
)
# ๊ธฐ๋ณธ ์ฐ๊ฒฐ ์ฌ์ฉ
conn = get_connection()
cur = conn.cursor(cursor_factory=DictCursor)
# ์ฐ๊ฒฐ ์ข
๋ฃ ์ฒ๋ฆฌ
def close_connection(conn, cur):
if cur is not None:
cur.close()
if conn is not None:
conn.close()
print("Database connection closed.")
# ์๋ฌ ์ฒ๋ฆฌ ์์
try:
conn = get_connection()
cur = conn.cursor()
# ๋ฐ์ดํฐ๋ฒ ์ด์ค ์์
์ํ
except psycopg2.DatabaseError as e:
print(f"Database error: {e}")
finally:
close_connection(conn, cur)
โ
ํน์ง:
- ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ ์ค์ ๋ฐ ๊ด๋ฆฌ
- ๋ค์ํ ์ปค์ ์ต์ ์ง์ (๊ธฐ๋ณธ, ๋์ ๋๋ฆฌ, ๋ช ๋ช ๋ ์ปค์)
- ๊ฐ๋ ฅํ ์๋ฌ ์ฒ๋ฆฌ ๋ฉ์ปค๋์ฆ
- ์๋ ๋ฐ ์๋ ํธ๋์ญ์ ๊ด๋ฆฌ
- ๋น๋๊ธฐ ์์ ์ง์
- ๋ค์ํ ๋ฐ์ดํฐ ํ์ ์ง์
- ์ฐ๊ฒฐ ํ๋ง ํตํฉ
- SSL ์ง์ ๋ณด์ ์ฐ๊ฒฐ
PostgreSQL์์์ ๊ธฐ๋ณธ์ ์ธ ๋ฐ์ดํฐ ์์ฑ, ์กฐํ, ์์ , ์ญ์ ์์
์ด๋ค.
# ํ
์ด๋ธ ์์ฑ
def create_tables():
commands = [
"""
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) UNIQUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""",
"""
CREATE TABLE IF NOT EXISTS posts (
id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
title VARCHAR(200) NOT NULL,
content TEXT,
published BOOLEAN DEFAULT FALSE,
views INTEGER DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
"""
]
try:
for command in commands:
cur.execute(command)
conn.commit()
print("Tables created successfully")
except (Exception, psycopg2.DatabaseError) as error:
print(f"Error: {error}")
conn.rollback()
# ๋ฐ์ดํฐ ์ฝ์
(Create)
def insert_user(name, email):
sql = """
INSERT INTO users (name, email)
VALUES (%s, %s)
RETURNING id, name, email, created_at
"""
try:
cur.execute(sql, (name, email))
user = cur.fetchone()
conn.commit()
return dict(user) if isinstance(cur, DictCursor) else user
except psycopg2.IntegrityError:
conn.rollback()
raise ValueError("์ค๋ณต๋ ์ด๋ฉ์ผ์ด๋ค.")
# ๋ฐ์ดํฐ ์กฐํ (Read)
def get_user(user_id):
cur.execute("""
SELECT id, name, email, created_at
FROM users
WHERE id = %s
""", (user_id,))
return cur.fetchone()
def get_all_users(limit=100, offset=0):
cur.execute("""
SELECT id, name, email, created_at
FROM users
ORDER BY created_at DESC
LIMIT %s OFFSET %s
""", (limit, offset))
return cur.fetchall()
# ๋ฐ์ดํฐ ์์ (Update)
def update_user(user_id, name=None, email=None):
updates = []
params = []
if name:
updates.append("name = %s")
params.append(name)
if email:
updates.append("email = %s")
params.append(email)
if not updates:
return None
params.append(user_id)
sql = f"""
UPDATE users
SET {", ".join(updates)}
WHERE id = %s
RETURNING id, name, email, created_at
"""
cur.execute(sql, params)
conn.commit()
return cur.fetchone()
# ๋ฐ์ดํฐ ์ญ์ (Delete)
def delete_user(user_id):
cur.execute("DELETE FROM users WHERE id = %s RETURNING id", (user_id,))
deleted_id = cur.fetchone()
conn.commit()
return deleted_id is not None
โ
ํน์ง:
- ํ ์ด๋ธ ์์ฑ ๋ฐ ์คํค๋ง ๊ด๋ฆฌ
- ํ๋ผ๋ฏธํฐํ๋ ์ฟผ๋ฆฌ๋ฅผ ํตํ SQL ์ธ์ ์ ๋ฐฉ์ง
- ๋ฐ์ดํฐ ์ฝ์ ๋ฐ ๋ฐํ ๊ฐ ์ฒ๋ฆฌ
- ๋ค์ํ ์กฐํ ์ต์ (๋จ์ผ, ๋ค์ค, ํ์ด์ง๋ค์ด์ )
- ๋์ ์ ๋ฐ์ดํธ ์ฟผ๋ฆฌ ๊ตฌ์ฑ
- ์์ ํ ์ญ์ ์์ ๋ฐ ๊ฒฐ๊ณผ ํ์ธ
- ์ธ๋ ํค ๋ฐ ์ฐธ์กฐ ๋ฌด๊ฒฐ์ฑ ๊ด๋ฆฌ
- ํธ๋์ญ์ ํ์ฉ ๋ฐ์ดํฐ ์ผ๊ด์ฑ ์ ์ง
PostgreSQL๋ง์ ๊ณ ๊ธ ๊ธฐ๋ฅ์ ํ์ฉํ์ฌ ๋ณต์กํ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์์
์ ์ํํ๋ค.
import json
from psycopg2.extras import Json, RealDictCursor
# JSON ๋ฐ์ดํฐ ์ฒ๋ฆฌ
def create_profile(user_id, profile_data):
"""์ฌ์ฉ์ ํ๋กํ์ JSON ํํ๋ก ์ ์ฅํ๋ค."""
sql = """
ALTER TABLE users
ADD COLUMN IF NOT EXISTS profile JSONB;
UPDATE users
SET profile = %s::jsonb
WHERE id = %s
RETURNING id, profile
"""
try:
# Json ํด๋์ค๋ฅผ ์ฌ์ฉํ์ฌ Python ๊ฐ์ฒด๋ฅผ PostgreSQL JSONB๋ก ๋ณํ
cur.execute(sql, (Json(profile_data), user_id))
result = cur.fetchone()
conn.commit()
return result
except Exception as e:
conn.rollback()
raise e
# JSON ํ๋ ์ฟผ๋ฆฌ
def find_users_by_skill(skill):
"""ํน์ ๊ธฐ์ ์ ๊ฐ์ง ์ฌ์ฉ์๋ฅผ ์ฐพ๋๋ค."""
sql = """
SELECT id, name, email, profile
FROM users
WHERE profile @> %s
"""
cur.execute(sql, (json.dumps({"skills": [skill]}),))
return cur.fetchall()
# JSON ํ๋ ์
๋ฐ์ดํธ
def add_user_skill(user_id, new_skill):
"""์ฌ์ฉ์ ํ๋กํ์ ์๋ก์ด ๊ธฐ์ ์ ์ถ๊ฐํ๋ค."""
sql = """
UPDATE users
SET profile = jsonb_set(
COALESCE(profile, '{}'::jsonb),
'{skills}',
COALESCE(profile->'skills', '[]'::jsonb) || %s::jsonb,
true
)
WHERE id = %s
RETURNING id, profile
"""
cur.execute(sql, (Json([new_skill]), user_id))
conn.commit()
return cur.fetchone()
# ์ ์ฒด ํ
์คํธ ๊ฒ์ ์ธ๋ฑ์ค ์์ฑ
def setup_text_search():
"""๊ฒ์๋ฌผ ๊ฒ์์ ์ํ ์ธ๋ฑ์ค๋ฅผ ์ค์ ํ๋ค."""
commands = [
"""
ALTER TABLE posts
ADD COLUMN IF NOT EXISTS search_vector tsvector;
""",
"""
CREATE INDEX IF NOT EXISTS posts_search_idx
ON posts USING GIN(search_vector);
""",
"""
CREATE OR REPLACE FUNCTION posts_search_update() RETURNS trigger AS $$
BEGIN
NEW.search_vector :=
setweight(to_tsvector('korean', COALESCE(NEW.title, '')), 'A') ||
setweight(to_tsvector('korean', COALESCE(NEW.content, '')), 'B');
RETURN NEW;
END
$$ LANGUAGE plpgsql;
""",
"""
DROP TRIGGER IF EXISTS posts_search_update ON posts;
""",
"""
CREATE TRIGGER posts_search_update
BEFORE INSERT OR UPDATE ON posts
FOR EACH ROW EXECUTE PROCEDURE posts_search_update();
"""
]
try:
for command in commands:
cur.execute(command)
conn.commit()
print("Text search setup complete")
except Exception as e:
conn.rollback()
print(f"Error setting up text search: {e}")
# ์ ์ฒด ํ
์คํธ ๊ฒ์ ์คํ
def search_posts(query, limit=10):
"""๊ฒ์๋ฌผ ๋ด์ฉ์ ๊ฒ์ํ๋ค."""
sql = """
SELECT id, title, content, ts_rank(search_vector, websearch_to_tsquery('korean', %s)) as rank
FROM posts
WHERE search_vector @@ websearch_to_tsquery('korean', %s)
ORDER BY rank DESC
LIMIT %s
"""
cur.execute(sql, (query, query, limit))
return cur.fetchall()
# ๋ฐฐ์ด ๋ฐ์ดํฐ ์ฒ๋ฆฌ
def add_tags(post_id, tags):
"""๊ฒ์๋ฌผ์ ํ๊ทธ ๋ฐฐ์ด์ ์ถ๊ฐํ๋ค."""
# ๋จผ์ ๋ฐฐ์ด ์ปฌ๋ผ ์ถ๊ฐ ํ์ธ
cur.execute("""
ALTER TABLE posts
ADD COLUMN IF NOT EXISTS tags TEXT[] DEFAULT '{}'::TEXT[];
""")
# ํ๊ทธ ์ถ๊ฐ ์์
sql = """
UPDATE posts
SET tags = %s
WHERE id = %s
RETURNING id, title, tags
"""
cur.execute(sql, (tags, post_id))
conn.commit()
return cur.fetchone()
# ๋ฐฐ์ด ๋ฐ์ดํฐ ๊ฒ์
def find_posts_by_tag(tag):
"""ํน์ ํ๊ทธ๊ฐ ํฌํจ๋ ๊ฒ์๋ฌผ์ ์ฐพ๋๋ค."""
sql = """
SELECT id, title, content, tags
FROM posts
WHERE %s = ANY(tags)
"""
cur.execute(sql, (tag,))
return cur.fetchall()
โ
ํน์ง:
- ๊ณ ๊ธ JSON/JSONB ๋ฐ์ดํฐ ์ฒ๋ฆฌ ๋ฐ ์ฟผ๋ฆฌ
- ๊ฐ๋ ฅํ ์ ์ฒด ํ ์คํธ ๊ฒ์ ๊ธฐ๋ฅ ๊ตฌํ
- ํธ๋ฆฌ๊ฑฐ์ ํจ์๋ฅผ ํ์ฉํ ์๋ ์ธ๋ฑ์ฑ
- ๋ฐฐ์ด ํ์ ๋ฐ์ดํฐ ์ ์ฅ ๋ฐ ๊ฒ์
- ๋ณต์กํ ๋ฐ์ดํฐ ๊ตฌ์กฐ ์กฐ์
- ๋ค๊ตญ์ด ํ ์คํธ ๊ฒ์ ์ง์
- ์คํค๋ง ๋์ ๋ณ๊ฒฝ ๋ฐ ๊ด๋ฆฌ
- ์ต์ ํ๋ ๊ฒ์ ์๊ณ ๋ฆฌ์ฆ ํ์ฉ
PostgreSQL์ ํธ๋์ญ์
๊ณผ ๋์์ฑ ์ ์ด๋ฅผ ํตํด ๋ฐ์ดํฐ ์ผ๊ด์ฑ๊ณผ ์ฑ๋ฅ์ ๊ด๋ฆฌํ๋ค.
# ๊ธฐ๋ณธ ํธ๋์ญ์
๊ด๋ฆฌ
def basic_transaction_example():
"""๊ธฐ๋ณธ์ ์ธ ํธ๋์ญ์
๊ด๋ฆฌ ์์"""
conn = get_connection()
try:
# ์๋ ์ปค๋ฐ ๋นํ์ฑํ
conn.autocommit = False
cur = conn.cursor()
# ํธ๋์ญ์
๋ด ์์
cur.execute("INSERT INTO users (name, email) VALUES (%s, %s)",
("์ฌ์ฉ์1", "[email protected]"))
cur.execute("INSERT INTO users (name, email) VALUES (%s, %s)",
("์ฌ์ฉ์2", "[email protected]"))
# ํธ๋์ญ์
์๋ฃ
conn.commit()
print("Transaction committed successfully")
except Exception as e:
# ์ค๋ฅ ๋ฐ์ ์ ๋กค๋ฐฑ
conn.rollback()
print(f"Transaction rolled back: {e}")
finally:
cur.close()
conn.close()
# ๋์์ฑ ์ ์ด๋ฅผ ์ํ ํ ์ ๊ธ
def transfer_money(from_account, to_account, amount):
"""๊ณ์ข ๊ฐ ์ก๊ธ ์์
์ ํธ๋์ญ์
์ผ๋ก ์ฒ๋ฆฌํ๋ค."""
conn = get_connection()
try:
conn.autocommit = False
cur = conn.cursor()
# ํธ๋์ญ์
๊ฒฉ๋ฆฌ ์์ค ์ค์
cur.execute("SET TRANSACTION ISOLATION LEVEL REPEATABLE READ")
# FOR UPDATE๋ฅผ ์ฌ์ฉํ ํ ์ ๊ธ์ผ๋ก ๋์ ์ ๊ทผ ์ ์ด
cur.execute("""
SELECT id, balance FROM accounts
WHERE id = %s FOR UPDATE
""", (from_account,))
from_acc = cur.fetchone()
if not from_acc:
raise ValueError(f"๊ณ์ข {from_account}๊ฐ ์กด์ฌํ์ง ์๋๋ค.")
from_balance = from_acc[1]
if from_balance < amount:
raise ValueError(f"์์ก ๋ถ์กฑ: ํ์ {amount}, ๋ณด์ {from_balance}")
# ์์ทจ ๊ณ์ข ํ์ธ ๋ฐ ์ ๊ธ
cur.execute("""
SELECT id, balance FROM accounts
WHERE id = %s FOR UPDATE
""", (to_account,))
to_acc = cur.fetchone()
if not to_acc:
raise ValueError(f"๊ณ์ข {to_account}๊ฐ ์กด์ฌํ์ง ์๋๋ค.")
# ์ถ๊ธ ์ฒ๋ฆฌ
cur.execute("""
UPDATE accounts
SET balance = balance - %s,
updated_at = CURRENT_TIMESTAMP
WHERE id = %s
""", (amount, from_account))
# ์
๊ธ ์ฒ๋ฆฌ
cur.execute("""
UPDATE accounts
SET balance = balance + %s,
updated_at = CURRENT_TIMESTAMP
WHERE id = %s
""", (amount, to_account))
# ๊ฑฐ๋ ๊ธฐ๋ก ์ ์ฅ
cur.execute("""
INSERT INTO transactions
(from_account, to_account, amount, transaction_date, status)
VALUES (%s, %s, %s, CURRENT_TIMESTAMP, 'completed')
RETURNING id
""", (from_account, to_account, amount))
transaction_id = cur.fetchone()[0]
# ๋ชจ๋ ์์
์ด ์ฑ๊ณตํ๋ฉด ์ปค๋ฐ
conn.commit()
return {
"transaction_id": transaction_id,
"from_account": from_account,
"to_account": to_account,
"amount": amount,
"status": "completed"
}
except Exception as e:
# ์ค๋ฅ ๋ฐ์ ์ ๋กค๋ฐฑ
conn.rollback()
# ์คํจํ ๊ฑฐ๋ ๊ธฐ๋ก
try:
cur.execute("""
INSERT INTO transactions
(from_account, to_account, amount, transaction_date, status, error_message)
VALUES (%s, %s, %s, CURRENT_TIMESTAMP, 'failed', %s)
""", (from_account, to_account, amount, str(e)))
conn.commit()
except:
pass
raise e
finally:
cur.close()
conn.close()
# ๋ฐ๋๋ฝ ๋ฐฉ์ง ์ ๋ต
def deadlock_prevention_example():
"""๋ฐ๋๋ฝ์ ๋ฐฉ์งํ๋ ํธ๋์ญ์
ํจํด"""
conn = get_connection()
try:
conn.autocommit = False
cur = conn.cursor()
# ํธ๋์ญ์
ํ์์์ ์ค์
cur.execute("SET statement_timeout = '5s'")
# ํญ์ ID ์ค๋ฆ์ฐจ์์ผ๋ก ์ ๊ธ์ ํ๋ํ์ฌ ๋ฐ๋๋ฝ ๋ฐฉ์ง
account_ids = sorted([account1_id, account2_id])
# ์ฒซ ๋ฒ์งธ ๊ณ์ข ์ ๊ธ
cur.execute("""
SELECT id, balance FROM accounts
WHERE id = %s FOR UPDATE
""", (account_ids[0],))
# ๋ ๋ฒ์งธ ๊ณ์ข ์ ๊ธ
cur.execute("""
SELECT id, balance FROM accounts
WHERE id = %s FOR UPDATE
""", (account_ids[1],))
# ์ดํ ์์
์ํ...
conn.commit()
except Exception as e:
conn.rollback()
raise e
finally:
cur.close()
conn.close()
# ๋๊ด์ ๋์์ฑ ์ ์ด
def optimistic_concurrency_control(user_id, new_data):
"""๋๊ด์ ๋์์ฑ ์ ์ด ํจํด ๊ตฌํ"""
conn = get_connection()
try:
conn.autocommit = False
cur = conn.cursor()
# ๋ฒ์ ํ์ธ
cur.execute("""
SELECT id, data, version FROM user_data
WHERE id = %s
""", (user_id,))
result = cur.fetchone()
if not result:
raise ValueError("์ฌ์ฉ์ ๋ฐ์ดํฐ๊ฐ ์กด์ฌํ์ง ์๋๋ค.")
current_version = result[2]
# ๋ฒ์ ์ ํ์ธํ๋ฉฐ ์
๋ฐ์ดํธ
updated_rows = cur.execute("""
UPDATE user_data
SET data = %s, version = version + 1, updated_at = CURRENT_TIMESTAMP
WHERE id = %s AND version = %s
""", (Json(new_data), user_id, current_version))
if cur.rowcount == 0:
# ๋ค๋ฅธ ํธ๋์ญ์
์ด ์ด๋ฏธ ๋ฐ์ดํฐ๋ฅผ ๋ณ๊ฒฝํ ๊ฒฝ์ฐ
conn.rollback()
return {"status": "conflict", "message": "Data was modified by another process"}
conn.commit()
return {"status": "success", "new_version": current_version + 1}
except Exception as e:
conn.rollback()
raise e
finally:
cur.close()
conn.close()
โ
ํน์ง:
- ACID ์์ฑ์ ๋ณด์ฅํ๋ ํธ๋์ญ์ ๊ด๋ฆฌ
- ๋ค์ํ ํธ๋์ญ์ ๊ฒฉ๋ฆฌ ์์ค ํ์ฉ
- ํ ๋จ์ ์ ๊ธ์ ํตํ ๋์์ฑ ์ ์ด
- ๋ฐ๋๋ฝ ๋ฐฉ์ง ์ ๋ต ๊ตฌํ
- ๋๊ด์ /๋น๊ด์ ๋์์ฑ ์ ์ด ํจํด
- ํธ๋์ญ์ ํ์์์ ์ค์
- ์ค๋ฅ ๋ฐ์ ์ ๋กค๋ฐฑ ๋ฉ์ปค๋์ฆ
- ํธ๋์ญ์ ๋ก๊น ๋ฐ ๋ชจ๋ํฐ๋ง
PostgreSQL ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์์ฉ ํ๋ก๊ทธ๋จ์ ์ฑ๋ฅ์ ์ต์ ํํ๋ ๊ธฐ๋ฒ์ด๋ค.
from psycopg2.extras import execute_values
import time
import statistics
# ๋๋ ์ฝ์
์ต์ ํ
def bulk_insert_users(users_data):
"""๋๋์ ์ฌ์ฉ์ ๋ฐ์ดํฐ๋ฅผ ํจ์จ์ ์ผ๋ก ์ฝ์
ํ๋ค."""
conn = get_connection()
try:
cur = conn.cursor()
# execute_values ์ฌ์ฉํ์ฌ ๋๋ ์ฝ์
insert_query = """
INSERT INTO users (name, email)
VALUES %s
RETURNING id
"""
# ๋ฐ์ดํฐ ๋ณํ
values = [(user['name'], user['email']) for user in users_data]
# ๋๋ ์ฝ์
์คํ
result = execute_values(cur, insert_query, values, fetch=True)
conn.commit()
return [row[0] for row in result] # ์ฝ์
๋ ID ๋ชฉ๋ก ๋ฐํ
except Exception as e:
conn.rollback()
raise e
finally:
cur.close()
conn.close()
# ์ฟผ๋ฆฌ ์ฑ๋ฅ ์ธก์
def measure_query_performance(query_func, *args, iterations=5):
"""์ฟผ๋ฆฌ ์ฑ๋ฅ์ ์ธก์ ํ๋ค."""
execution_times = []
for i in range(iterations):
start_time = time.time()
query_func(*args)
end_time = time.time()
execution_times.append(end_time - start_time)
avg_time = statistics.mean(execution_times)
min_time = min(execution_times)
max_time = max(execution_times)
return {
"average": avg_time,
"min": min_time,
"max": max_time,
"total_iterations": iterations
}
# ์ปค๋ฅ์
ํ๋ง ๊ตฌํ
from contextlib import contextmanager
from psycopg2.pool import ThreadedConnectionPool
# ์ฑ๊ธํค ์ปค๋ฅ์
ํ
class ConnectionPool:
_instance = None
_pool = None
@classmethod
def get_instance(cls, min_conn=1, max_conn=10, **kwargs):
if cls._instance is None:
cls._instance = cls(min_conn, max_conn, **kwargs)
return cls._instance
def __init__(self, min_conn, max_conn, **kwargs):
self._pool = ThreadedConnectionPool(
min_conn,
max_conn,
dbname="mydatabase",
user="username",
password="password",
host="localhost",
port="5432",
**kwargs
)
def get_connection(self):
return self._pool.getconn()
def release_connection(self, conn):
self._pool.putconn(conn)
def close_all(self):
self._pool.closeall()
@contextmanager
def get_db_connection():
"""๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ์ ์ปจํ
์คํธ ๋งค๋์ ๋ก ์ ๊ณตํ๋ค."""
pool = ConnectionPool.get_instance()
conn = pool.get_connection()
try:
yield conn
finally:
pool.release_connection(conn)
@contextmanager
def get_db_cursor(commit=True):
"""๋ฐ์ดํฐ๋ฒ ์ด์ค ์ปค์๋ฅผ ์ปจํ
์คํธ ๋งค๋์ ๋ก ์ ๊ณตํ๋ค."""
with get_db_connection() as conn:
cur = conn.cursor()
try:
yield cur
if commit:
conn.commit()
except:
conn.rollback()
raise
finally:
cur.close()
# ์ธ๋ฑ์ค ์์ฑ ๋ฐ ๊ด๋ฆฌ
def optimize_database_indexes():
"""๋ฐ์ดํฐ๋ฒ ์ด์ค ์ธ๋ฑ์ค๋ฅผ ์ต์ ํํ๋ค."""
with get_db_cursor() as cur:
# ์์ฃผ ๊ฒ์๋๋ ํ๋์ ์ธ๋ฑ์ค ์์ฑ
cur.execute("""
CREATE INDEX IF NOT EXISTS users_email_idx ON users(email);
CREATE INDEX IF NOT EXISTS posts_user_id_idx ON posts(user_id);
CREATE INDEX IF NOT EXISTS posts_created_at_idx ON posts(created_at);
CREATE INDEX IF NOT EXISTS posts_title_idx ON posts USING gin(to_tsvector('korean', title));
""")
# ๋ณตํฉ ์ธ๋ฑ์ค ์์ฑ
cur.execute("""
CREATE INDEX IF NOT EXISTS posts_user_published_idx
ON posts(user_id, published);
""")
โ
ํน์ง:
- ๋๋ ๋ฐ์ดํฐ ์์ ์ต์ ํ
- ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ ํ๋ง ๊ตฌํ
- ์ธ๋ฑ์ค ์ ๋ต ์๋ฆฝ ๋ฐ ๊ด๋ฆฌ
- ์ฟผ๋ฆฌ ์ฑ๋ฅ ์ธก์ ๋ฐ ๋ถ์
- ์ปจํ ์คํธ ๋งค๋์ ๋ฅผ ํ์ฉํ ๋ฆฌ์์ค ๊ด๋ฆฌ
- ํจ์จ์ ์ธ ํธ๋์ญ์ ์ฌ์ฉ
- ๋น๋๊ธฐ ์์ ์ฒ๋ฆฌ ๋ฐฉ๋ฒ
- ์ฑ๋ฅ ๋ฌธ์ ์ง๋จ ๋ฐ ํด๊ฒฐ ์ ๋ต
โ
๋ชจ๋ฒ ์ฌ๋ก:
- ์ปค๋ฅ์
ํ๋ง ์ฌ์ฉ
- ์ฐ๊ฒฐ ์์ฑ๊ณผ ํด์ ๋น์ฉ ๊ฐ์
- ๋์ ์ฐ๊ฒฐ ์ ์ ํ ๋ฐ ๊ด๋ฆฌ
- Thread-safe ์ฐ๊ฒฐ ์ฒ๋ฆฌ
- ์ ํ๋ฆฌ์ผ์ด์ ์ฑ๋ฅ ํฅ์
- ํ๋ผ๋ฏธํฐํ๋ ์ฟผ๋ฆฌ ์ฌ์ฉ
- SQL ์ธ์ ์ ๋ฐฉ์ง
- ์ฟผ๋ฆฌ ์ต์ ํ ๋ฐ ์ฌ์ฌ์ฉ
- ์๋ฒ ์ธก ์ค๋น๋ ๊ตฌ๋ฌธ ํ์ฉ
- ๋ฐ์ดํฐ ํ์ ์์ ์ฑ ํ๋ณด
- ํธ๋์ญ์
์ ์ ํ ํ์ฉ
- ๋ฐ์ดํฐ ์ผ๊ด์ฑ ๋ณด์ฅ
- ์์ ๋จ์ ๊ด๋ฆฌ
- ์ค๋ฅ ๋ณต๊ตฌ ์ฉ์ด
- ๋์์ฑ ๋ฌธ์ ํด๊ฒฐ
- ์ธ๋ฑ์ค ์ต์ ํ
- ์์ฃผ ์กฐํ๋๋ ํ๋ ์ธ๋ฑ์ฑ
- ๋ณตํฉ ์ธ๋ฑ์ค ์ ๋ต ์๋ฆฝ
- ๋ถํ์ํ ์ธ๋ฑ์ค ์ ๊ฑฐ
- ์ฟผ๋ฆฌ ์คํ ๊ณํ ๋ถ์
- ์ฃผ๊ธฐ์ ์ธ ๋ฐฑ์
- pg_dump ํ์ฉ ์๋ํ
- ์ฆ๋ถ ๋ฐฑ์ ์ ๋ต ๊ตฌํ
- ๋ณต๊ตฌ ํ๋ก์ธ์ค ํ ์คํธ
- ๋ฐฑ์ ๋ฌด๊ฒฐ์ฑ ๊ฒ์ฆ
- ๋ณด์ ์ค์ ํ์ธ
- ์ต์ ๊ถํ ์์น ์ ์ฉ
- ์ ๊ทผ ์ ์ด ์ค์
- SSL ์ฐ๊ฒฐ ์ค์
- ๋น๋ฐ๋ฒํธ ์ ์ฑ ๊ตฌํ
- ์ฑ๋ฅ ๋ชจ๋ํฐ๋ง
- ์ฌ๋ก์ฐ ์ฟผ๋ฆฌ ๋ก๊น
- ๋ฆฌ์์ค ์ฌ์ฉ๋ ๋ถ์
- ๋ณ๋ชฉ ํ์ ์๋ณ
- EXPLAIN ANALYZE ํ์ฉ
- ์๋ฌ ์ฒ๋ฆฌ ๊ตฌํ
- ์์ธ ์ฒ๋ฆฌ ํจํด ์ ์ฉ
- ์ธ๋ถํ๋ ์ค๋ฅ ์ฝ๋ ํ์ฉ
- ์ฐ๊ฒฐ ์ค๋ฅ ๊ด๋ฆฌ
- ์ฌ์๋ ๋ก์ง ๊ตฌํ
- ๋๋ ์์
์ต์ ํ
- execute_values ์ฌ์ฉ
- ๋ฐฐ์น ์ฒ๋ฆฌ ๊ตฌํ
- COPY ๋ช ๋ น ํ์ฉ
- ์์ ํ ์ด๋ธ ํ์ฉ
- ๋น๋๊ธฐ ์ฒ๋ฆฌ ํ์ฉ
- ๋ณ๋ ฌ ์ฟผ๋ฆฌ ์คํ
- ๋น๋๊ธฐ ์ปค๋ฅ์ ํ
- ๋ฐฑ๊ทธ๋ผ์ด๋ ์์ ์
- ์ด๋ฒคํธ ๋๋ฆฌ๋ธ ์ํคํ ์ฒ