MySQL 핵심 정리

MySQL은 대표적인 관계형 데이터베이스 관리 시스템(RDBMS)이다.

서버 애플리케이션에서 사용자 정보, 주문, 결제, 게임 데이터처럼 데이터 사이의 관계와 정합성이 중요한 데이터를 저장하고 조회하는 데 많이 사용한다.

MySQL을 이해하려면 SQL 문법만 외우는 것보다 테이블, 관계, Index, Transaction, Lock이 실제로 어떻게 연결되는지를 이해하는 것이 중요하다.

기본적인 서버 구조는 다음과 같다.

Client -> HTTP Request -> Application Server -> MySQL Driver -> MySQL -> Storage

애플리케이션이 직접 데이터 파일을 수정하는 것이 아니라 SQL을 통해 MySQL에 작업을 요청하고, MySQL이 데이터를 관리한다.


관계형 데이터베이스

관계형 데이터베이스는 데이터를 Table 형태로 관리한다.

예를 들어 사용자 정보를 저장한다면:

각 Row는 하나의 데이터를 의미하고 Column은 데이터의 속성을 나타낸다.

Table -> Row -> Column

관계형 데이터베이스의 특징은 여러 Table 사이의 관계를 명확하게 표현할 수 있다는 것이다.

예를 들어 사용자와 주문을 분리하면:

users -> id -> 1:N -> orders -> user_id

orders.user_id를 통해 어떤 사용자의 주문인지 연결할 수 있다.


Database와 Table

MySQL에서는 하나의 서버 안에 여러 Database를 만들 수 있고 Database 안에 여러 Table을 만들 수 있다.

MySQL Server -> game -> users / characters / items
MySQL Server -> analytics -> events / statistics

Table은 실제 데이터를 저장하는 기본적인 구조다.

예를 들어:

CREATE TABLE users (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(255) NOT NULL
);

이렇게 Table의 Column과 데이터 타입, 제약 조건을 정의한다.


Primary Key

Primary Key는 Table에서 각 Row를 고유하게 식별하기 위한 Key다.

CREATE TABLE users (
    id BIGINT PRIMARY KEY,
    name VARCHAR(100)
);

다음과 같은 데이터가 있다면:

id가 각 사용자를 구분한다.

Primary Key는 중복될 수 없고 NULL을 가질 수 없다.

실무에서는 숫자형 ID나 UUID 등을 Primary Key로 사용하는 경우가 많다.


Foreign Key

Foreign Key는 다른 Table과의 관계를 표현하는 데 사용한다.

예를 들어:

users -> id -> 1 / 2 / 3
orders -> id / user_id -> 10 / 1, 11 / 1, 12 / 2

orders.user_idusers.id를 참조한다.

SQL로 표현하면:

CREATE TABLE orders (
    id BIGINT PRIMARY KEY,
    user_id BIGINT NOT NULL,

    FOREIGN KEY (user_id)
        REFERENCES users(id)
);

이렇게 하면 데이터베이스가 Table 사이의 참조 관계를 관리할 수 있다.

다만 실제 서비스에서는 성능이나 운영 방식 때문에 Foreign Key를 반드시 사용하는 것은 아니다.

중요한 것은 데이터 관계와 무결성을 어떻게 보장할 것인지다.


SQL

MySQL은 SQL을 사용해서 데이터를 조회하고 변경한다.

데이터를 조회하려면:

SELECT *
FROM users;

조건을 걸 수 있다.

SELECT *
FROM users
WHERE id = 10;

데이터를 추가하려면:

INSERT INTO users (name, email)
VALUES ('Joo', 'joo@example.com');

수정:

UPDATE users
SET name = 'Kim'
WHERE id = 10;

삭제:

DELETE FROM users
WHERE id = 10;

이 네 가지가 기본적인 CRUD다.


JOIN

관계형 데이터베이스에서 중요한 기능이 JOIN이다.

사용자와 주문이 서로 다른 Table에 있다고 하자.

users -> id / name
orders -> id / user_id / price

사용자 이름과 주문 금액을 같이 조회하려면:

SELECT
    users.name,
    orders.price
FROM users
JOIN orders
    ON users.id = orders.user_id;

결과는 다음과 같이 나온다.

Table을 분리해서 관리하면서 Query에서 필요한 데이터를 연결할 수 있다는 것이 관계형 DB의 중요한 특징이다.


정규화

관계형 데이터베이스에서는 데이터를 여러 Table로 분리하는 경우가 많다.

예를 들어 주문 데이터에 사용자 이름을 그대로 저장한다고 하면:

orders -> order_id / user_name -> 1 / Joo, 2 / Joo, 3 / Joo

사용자 이름이 변경되면 여러 Row를 수정해야 한다.

대신 사용자와 주문을 분리할 수 있다.

users -> id / name -> 1 / Joo
orders -> id / user_id -> 1 / 1, 2 / 1, 3 / 1

필요할 때 JOIN한다.

이처럼 중복 데이터를 줄이고 데이터의 관계를 명확하게 만드는 것이 정규화의 기본적인 목적이다.

다만 정규화가 무조건 많이 될수록 좋은 것은 아니다.

조회 성능이나 데이터 접근 패턴 때문에 일부 데이터를 중복 저장하는 비정규화도 사용할 수 있다.


Index

데이터가 많아지면 Query 성능에서 Index가 중요해진다.

예를 들어 사용자 1,000만 명이 있는 Table에서:

SELECT *
FROM users
WHERE email = 'joo@example.com';

을 실행한다고 하자.

email에 적절한 Index가 없다면 많은 Row를 확인해야 할 수 있다.

Index를 만들면:

CREATE INDEX idx_users_email
ON users(email);

MySQL이 해당 조건을 찾는 데 Index를 활용할 수 있다.

개념적으로는:

Table -> 10,000,000 Rows -> Index -> 필요한 Row

Index는 검색 성능을 높이는 대신 저장 공간을 사용하고 INSERT, UPDATE, DELETE 시 Index도 갱신해야 한다.

따라서 모든 Column에 Index를 만드는 것은 좋은 설계가 아니다.

실제로 자주 사용하는 WHERE, JOIN, ORDER BY 등의 Query 패턴을 보고 설계해야 한다.


Composite Index

여러 Column을 함께 사용하는 Query라면 Composite Index를 고려할 수 있다.

CREATE INDEX idx_users_class_level
ON users(class, level);

다음과 같은 Query가 있다고 하자.

SELECT *
FROM users
WHERE class = 'Knight'
  AND level >= 30;

이런 Query 패턴에서는 Composite Index가 도움이 될 수 있다.

다만 Composite Index에는 Column 순서가 중요하다.

(class, level) != (level, class)

은 동일한 Index가 아니다.

따라서 Index를 만들 때는 실제 Query에서 어떤 조건과 정렬을 사용하는지부터 확인해야 한다.


Transaction

Transaction은 여러 데이터베이스 작업을 하나의 작업 단위로 묶는 기능이다.

대표적으로 다음 네 가지 특성을 ACID라고 한다.

Atomicity -> Consistency -> Isolation -> Durability

예를 들어 게임에서 아이템을 거래한다고 하자.

Player A -> 아이템 차감
Player B -> 아이템 지급

A의 아이템만 차감되고 B에게 지급되지 않으면 데이터가 잘못된다.

두 작업을 하나의 Transaction으로 묶을 수 있다.

START TRANSACTION;

UPDATE inventories
SET item_count = item_count - 1
WHERE user_id = 100;

UPDATE inventories
SET item_count = item_count + 1
WHERE user_id = 200;

COMMIT;

중간에 문제가 발생하면:

ROLLBACK;

을 통해 Transaction을 취소할 수 있다.

Transaction -> 작업 A -> 작업 B -> 성공 -> COMMIT
Transaction -> 작업 A -> 작업 B -> 실패 -> ROLLBACK

은행 송금, 결제, 아이템 거래처럼 여러 데이터를 함께 변경해야 하는 작업에서 중요하다.


Isolation과 Lock

Transaction을 이해하면 자연스럽게 Isolation과 Lock을 만나게 된다.

여러 사용자가 동시에 같은 데이터를 수정할 수 있기 때문이다.

예를 들어:

User A -> 상품 구매
User B -> 같은 상품 구매

재고가 1개인데 두 요청이 동시에 처리되면 문제가 발생할 수 있다.

MySQL의 InnoDB는 Lock과 Transaction Isolation을 이용해 이런 동시성 문제를 제어한다.

대표적인 Isolation Level은:

READ UNCOMMITTED -> READ COMMITTED -> REPEATABLE READ -> SERIALIZABLE

MySQL InnoDB의 기본 Isolation Level은 일반적으로 REPEATABLE READ다.

Isolation Level에 따라 다른 Transaction의 변경 내용을 어느 시점에 볼 수 있는지와 동시성 제어 방식이 달라진다.

실무에서는 단순히 “Lock을 걸면 안전하다”라고 생각하기보다 어떤 Transaction이 어떤 순서로 데이터를 읽고 수정하는지를 보는 것이 중요하다.


InnoDB

MySQL에서는 Storage Engine이라는 개념이 있다.

대표적인 Storage Engine이 InnoDB다.

InnoDB는 Transaction, Row-level Locking, Foreign Key 등을 지원한다.

일반적인 서버 애플리케이션에서는 InnoDB를 사용하는 경우가 많다.

MySQL -> InnoDB -> Data / Index / Lock / Transaction

MySQL 자체와 Storage Engine을 구분해서 이해하면 MySQL의 내부 구조를 볼 때 도움이 된다.


Connection과 Connection Pool

애플리케이션 서버가 MySQL에 Query를 보내려면 DB Connection이 필요하다.

단순하게 보면:

Application -> Connection -> MySQL

매 요청마다 Connection을 새로 만들고 닫는다면 비용이 커질 수 있다.

그래서 일반적으로 Connection Pool을 사용한다.

Application -> Connection Pool -> C1 / C2 / C3 / C4 -> MySQL

요청이 들어오면 Pool에서 Connection을 빌리고 Query를 실행한 뒤 Connection을 다시 Pool에 반환한다.

Spring Boot에서는 HikariCP 같은 Connection Pool이 많이 사용된다.


MySQL과 서버 애플리케이션

실제 서버에서는 애플리케이션이 MySQL에 직접 SQL을 날리는 구조보다 데이터 접근 계층을 분리하는 경우가 많다.

Client -> HTTP -> Controller -> Service -> Repository / DAO -> MySQL

예를 들어 사용자를 조회한다고 하면:

GET /users/100 -> Controller -> UserService -> UserRepository -> MySQL

Repository에서 Query를 실행하고 결과를 애플리케이션 객체로 변환한다.

이렇게 역할을 분리하면 HTTP 처리 코드와 데이터베이스 접근 코드가 섞이지 않는다.


ORM과 MySQL

서버 개발에서는 MySQL을 직접 SQL로 다루는 방식 외에 ORM을 사용하는 경우도 많다.

대표적으로 Java에서는 JPA/Hibernate가 있다.

예를 들어 객체를:

class User -> Long id -> String name

처럼 정의하고 ORM이 객체와 데이터베이스 Table 사이의 매핑을 관리하도록 할 수 있다.

Application Object -> ORM -> SQL -> MySQL

하지만 ORM을 사용한다고 SQL이 사라지는 것은 아니다.

결국 MySQL은 SQL을 실행한다.

복잡한 Query나 성능 문제를 다루려면 JOIN, Index, Execution Plan 같은 데이터베이스 자체의 개념을 알아야 한다.


EXPLAIN

Query 성능을 분석할 때 EXPLAIN을 사용할 수 있다.

EXPLAIN
SELECT *
FROM users
WHERE email = 'joo@example.com';

실행 계획을 확인하면 MySQL이 해당 Query를 어떻게 처리하려고 하는지 볼 수 있다.

예를 들어 Index를 사용하는지, 어떤 순서로 Table을 접근하는지 등을 확인할 수 있다.

SQL -> EXPLAIN -> Execution Plan -> Index 사용 여부 / 접근 방식 / 예상 Row 수 / JOIN 순서

Query가 느리다고 해서 무조건 서버 코드를 수정할 것이 아니라 데이터베이스가 해당 Query를 어떻게 실행하는지부터 확인할 필요가 있다.


MySQL의 동시성

서버에는 동시에 여러 요청이 들어온다.

Request A / Request B / Request C / Request D -> MySQL

MySQL은 여러 Connection의 Query를 동시에 처리한다.

이 과정에서 Transaction, Lock, Index, Isolation Level이 서로 연결된다.

예를 들어 같은 Row를 동시에 수정하는 경우 Lock이 필요할 수 있고, Transaction이 너무 오래 유지되면 다른 요청의 처리까지 지연될 수 있다.

그래서 서버 성능을 볼 때 단순히 Query 하나의 실행 시간만 보는 것이 아니라:

Connection Pool -> Concurrency -> Transaction -> Lock -> Query -> Index

전체 흐름을 같이 봐야 한다.


MySQL과 Redis의 역할

MySQL과 Redis는 서버에서 함께 사용하는 경우가 많다.

MySQL은 데이터를 영구적으로 관리하는 주 저장소로 사용하고, Redis는 자주 사용하는 데이터를 빠르게 가져오기 위한 Cache나 Session 등에 사용할 수 있다.

Application -> Redis -> Cache
Application -> MySQL -> Source of Data

예를 들어 상품 정보를 조회할 때:

Request -> Redis -> Cache Hit -> Return
Request -> Redis -> Cache Miss -> MySQL -> Redis에 저장 -> Return

Redis가 MySQL을 완전히 대체하는 구조라기보다는 각각의 목적에 맞게 사용하는 것이다.


MySQL의 전체 구조

서버 애플리케이션에서 MySQL을 사용하는 전체 흐름을 묶어보면 다음과 같다.

Client -> HTTP -> Application Server -> Connection Pool -> MySQL -> InnoDB -> Data / Index
MySQL -> InnoDB -> Transaction -> Lock

데이터 모델은:

Database -> Table -> Row / Column -> Primary Key / Foreign Key

Query는:

SELECT / INSERT / UPDATE / DELETE -> JOIN -> Index / EXPLAIN

동시성 처리는:

Transaction -> Isolation -> Lock

이렇게 연결해서 보면 MySQL의 주요 개념이 하나의 구조로 정리된다.


정리

MySQL에서 자주 사용하는 핵심 개념은 다음과 같다.

RDBMS -> Database -> Table -> Row / Column -> Primary Key / Foreign Key

Query와 성능 측면에서는:

SQL -> SELECT / INSERT / UPDATE / DELETE -> JOIN -> Index -> Composite Index / EXPLAIN

데이터의 정합성과 동시성에서는:

Transaction -> ACID / Isolation / Lock

그리고 서버 애플리케이션에서는:

Client -> Application Server -> Connection Pool -> Repository / DAO -> MySQL

정도로 연결하면 된다.

MySQL을 공부할 때 SQL 문법만 익히면 데이터베이스를 사용하는 수준에 머물게 된다. 실제 서버 개발에서는 데이터를 어떻게 모델링할지, 어떤 Query가 실행되는지, Index가 어떻게 사용되는지, 여러 요청이 동시에 들어왔을 때 데이터가 어떻게 보호되는지까지 봐야 한다.

결국 MySQL의 핵심은 Table과 SQL 문법 자체보다 데이터의 관계, 정합성, 동시성, 그리고 Query 성능을 관리하는 것에 있다.