Skip to content

데이터베이스 기초 (인덱스 / 트랜잭션 / 쿼리 최적화)

🎯 핵심 질문

왜 Excel 쿼리는 10초가 걸리는데, 쇼핑몰 검색은 0.01초밖에 안 걸릴까? 데이터가 "수천 건"에서 "10억 건"으로, "1인 사용"에서 "수천만 명의 동시 접속"으로 변하면 Excel로는 부족합니다. 데이터베이스는 바로 이 문제를 해결하기 위해 탄생했습니다 — 대량의 데이터와 높은 동시성을 처리하는 "슈퍼 Excel"입니다. 이 장에서는 데이터베이스의 핵심 원리를 처음부터 알기 쉽게 설명합니다.


1. 왜 "데이터베이스"가 필요한가?

1.1 작은 서점에서 쇼핑몰까지: 데이터 규모의 변화

작은 서점을 운영한다고 상상해 보세요. 하루에 몇 권의 책을 팔고, 노트에 기록합니다:

2024-01-15: 홍길동이 《백년의 고독》을 구매, 59위안
2024-01-16: 이순신이 《삶》을 구매, 39위안

이때는 노트로 충분합니다. 하지만 서점이 "아마존"이 되어 하루에 백만 건의 주문이 쏟아지면 문제가 생깁니다:

  • 데이터량: 수십 줄이 아니라 수억 줄
  • 동시 접속: 한 명이 조회하는 것이 아니라 수천만 명이 동시에 접속
  • 데이터 연관: 주문은 사용자, 상품, 재고, 물류와 연결... 복잡한 관계를 효율적으로 관리해야 함
  • 데이터 안전: 정전으로 모든 주문을 잃어서는 안 됨

📓 Excel/노트

  • 개인이나 소규모 팀에 적합
  • 데이터량: 수천~수만 행
  • 1인 사용, 순차 접근
  • 수동 검색, 속도가 느림

🗄️ 데이터베이스

  • 엔터프라이즈급 애플리케이션에 적합
  • 데이터량: 수억 건 이상
  • 수천만 명이 동시에 온라인 접속
  • 밀리초 단위의 쿼리 속도

이것이 "데이터베이스"가 해결하는 문제입니다: 대량의 데이터를 효율적으로 저장하고, 빠르게 조회하며, 안전하게 관리하려면 어떻게 해야 하는가?

1.2 실제 실패 사례: 왜 Excel로 사용자 데이터를 저장하면 안 되는가

"내 프로젝트는 사용자가 몇 만 명뿐이라 Excel로 충분하지 않나요?"라고 말할 수 있습니다. 실제 이야기를 들려드리겠습니다.

김 대리의 창업 실패기

김 대리가 소셜 앱을 창업했는데, 초기에는 사용자가 많지 않아 Excel로 사용자 정보(이름, 전화번호, 가입 시간 등)를 저장했습니다. 매일 Excel를 내보내서 사용자 증가를 통계내고, 모든 게 정상이었습니다.

사용자가 10만 명을 돌파하자 문제가 나타나기 시작했습니다:

  • Excel를 여는 데 5분 대기
  • "서울 사용자"를 필터링하는 데 한참 멈춤
  • 한 번은 Excel 파일이 손상되어 수천 명의 사용자 데이터가 영구적으로 유실

가장 치명적인 것은 "특정 사용자의 모든 주문 보기" 기능을 구현하려 했는데 — 사용자 정보와 주문이 서로 다른 Excel 시트에 있어, 수동으로 복사-붙여넣기만 가능했고 매번 30분이 걸렸습니다.

선배에게 조언을 구하자, 선배는 한 번 보고 웃으며 말했습니다. "네가 필요한 건 Excel가 아니라 데이터베이스야."

데이터베이스로 전환한 후 모든 것이 바뀌었습니다:

  • "서울 사용자" 조회는 0.01초밖에 안 걸림
  • "관계"를 통해 사용자와 주문이 자동으로 연결되어, SQL 문 하나로 해결
  • 데이터가 자동 백업되어 파일 손상 걱정 끝

김 대리는 이道理를 깨달았습니다: 데이터가 적을 때는 아무거나 써도 되지만, 데이터가 커지면 Excel은 재앙입니다.

💡 핵심 교훈

데이터베이스는 "더 복잡한 Excel"가 아니라 완전히 다른 설계 철학입니다:

  • Excel: 소규모 데이터, 1인 사용을 위해 설계
  • 데이터베이스: 대규모 데이터, 높은 동시성, 복잡한 연관을 위해 설계

적절한 도구를 선택하면 시스템 성능을 수천 배에서 수만 배까지 향상시킬 수 있습니다.


2. 핵심 개념: 테이블, 행, 열, 기본키

🤔 이 개념들이 데이터베이스와 무슨 관련이 있나요?

테이블, 행, 열, 기본키는 데이터베이스의 "블록"입니다.

집을 짓는다고 상상해 보세요:

  • 테이블 = 하나의 방(한 종류의 데이터를 저장)
  • = 방 안의 상자 하나(하나의 완전한 기록)
  • = 상자의 라벨(이름, 나이 등)
  • 기본키 = 상자의 고유 번호(절대 중복되지 않음)

이 기초 개념을 이해해야 데이터가 어떻게 구성되어 있는지 알 수 있습니다.

데이터베이스를 깊이 배우기 전에 먼저 이 핵심 개념들을 확실히 이해해야 합니다. 이해를 돕기 위해 도서관에 비유해 보겠습니다.

2.1 도서관 비유로 데이터베이스 구조 이해하기

도서관에 들어간다고 상상해 보세요. 그 안의 조직은 데이터베이스와 놀라울 정도로 비슷합니다:

개념📚 도서관 비유실제 역할구체적 예시
데이터베이스(Database)도서관 전체모든 데이터를 저장하는 컨테이너이커머스 웹사이트의 데이터베이스
테이블(Table)하나의 책장같은 종류의 데이터 집합사용자 테이블, 상품 테이블, 주문 테이블
열(Column)책등의 라벨데이터의 속성(필드)이름, 나이, 전화번호
행(Row)책장의 각 책하나의 구체적인 데이터 기록"홍길동, 25세, 서울"
기본키(Primary Key)각 책의 ISBN 번호각 행을 유일하게 식별하는 IDuser_id = 1001

실제 예시 보기: 사용자 테이블(users)

user_id (기본키)nameagecityemail
1001홍길동25서울hong@example.com
1002이순신30부산lee@example.com
1003강감찬28서울kang@example.com
  • 테이블: users(모든 사용자 데이터 저장)
  • : user_id, name, age, city, email(각 사용자의 속성)
  • : 각 행은 한 명의 사용자(예: "홍길동, 25세, 서울")
  • 기본키: user_id(1001, 1002, 1003, 절대 중복되지 않음)

2.2 기본키(Primary Key): 데이터의 "주민등록번호"

📖 기본키란?

기본키는 테이블에서 각 행의 고유 식별자로, 주민등록번호와 같습니다.

핵심 특징:

  • 유일성: 절대 중복되지 않음(같은 주민등록번호를 가진 두 사람은 없음)
  • 비어 있지 않음: 반드시 값이 있어야 함("주민등록번호가 없는" 사람은 있을 수 없음)
  • 불변성: 한 번 설정되면 수정되지 않음(주민등록번호는 변하지 않음)

일반적 방법:

  • 자동 증가 정수 사용: 1, 2, 3, 4...
  • UUID(전 세계적으로 유일한 식별자) 사용: 550e8400-e29b-41d4-a716-446655440000

왜 기본키가 필요할까요? 기본키가 없는 세계를 상상해 보세요:

시나리오: "홍길동"의 나이를 수정하고 싶은데, 테이블에 "홍길동"이 3명 있습니다. 시스템은 어느 쪽을 수정해야 할까요?

sql
-- 기본키가 없으면, "홍길동"이라는 이름을 가진 모든 사람이 동시에 수정됨!
UPDATE users SET age = 26 WHERE name = '홍길동';

-- 기본키가 있으면, 정확히 수정 가능
UPDATE users SET age = 26 WHERE user_id = 1001;

기본키의 황금 법칙: 모든 테이블에는 기본키가 있어야 하며, 기본키는 절대 수정하지 마세요.

2.3 외래키(Foreign Key): 테이블을 연결하는 다리

이것이 데이터베이스가 Excel보다 강력한 핵심 — 테이블 간에 관계를 설정할 수 있습니다.

📖 외래키란?

외래키는 다른 테이블의 기본키를 가리키는 열로, 테이블 간의 연관을 설정하는 데 사용됩니다.

간단히 이해하기:

  • 기본키 = 나의 주민등록번호
  • 외래키 = 내가 참조하는 다른 사람의 주민등록번호

예시: 주문 테이블의 user_id가 바로 외래키로, 사용자 테이블의 기본키를 가리킵니다.

실제 예시를 살펴보세요:

사용자 테이블(users):

user_id (기본키)namephone
1001홍길동010xxxx
1002이순신011xxxx

주문 테이블(orders):

order_id (기본키)product_namepriceuser_id (외래키)
5001iPhone 1559991001
5002MacBook149991001
5003AirPods19991002

핵심 이해:

  • 주문 테이블의 user_id = 1001은 사용자 테이블의 user_id = 1001(홍길동)을 가리킴
  • "주문 5001은 누가 샀나"를 조회할 때, 데이터베이스가 자동으로 사용자 테이블에서 user_id = 1001인 사용자를 찾음

장점:

  • 데이터 중복 없음: 홍길동이 100개의 상품을 구매해도 그의 정보는 사용자 테이블에 한 번만 저장
  • 유지보수 용이: 홍길동이 전화번호를 바꾸면 사용자 테이블만 수정하면 되고, 모든 주문이 자동으로 새 번호와 연결
  • 유연한 쿼리: "각 사용자의 총 소비액은 얼마인가" 같은 복잡한 질문에 쉽게 답할 수 있음
🔗外键关系演示理解表与表之间如何关联
想象你在管理一个家族谱系:有"家谱表"记录每个人,有"婚姻表"记录谁和谁结婚了。两张表通过"人名"关联起来,这就是外键的作用。
👥用户表 (users)主表
🔑 user_id
name
phone
address
101
张三
138xxxx
北京
102
李四
139xxxx
上海
103
王五
137xxxx
广州
user_id (外键) → user_id (主键)
📦订单表 (orders)从表
🔑 order_id
book_name
🔗 user_id
price
001
百年孤独
101
59
002
活着
101
39
003
三体
101
99
004
百年孤独
102
59
005
红楼梦
102
79
006
西游记
103
69
💡 核心概念

主键(Primary Key):用户表的 user_id 是主键,唯一标识每个用户。

外键(Foreign Key):订单表的 user_id 是外键,指向用户表的主键。

关联查询:通过外键,数据库可以快速找到"订单 001 是用户 101 买的",然后去用户表查到"用户 101 是张三"。

🎯核心优势:外键消除了数据冗余。张三的地址只存一次,无论他买多少本书。如果要修改地址,只需改用户表的一行,所有订单自动关联到新地址。

3. 데이터베이스와 대화하는 방법? SQL 입문과 실전

데이터베이스를 마우스로 "클릭"해서 직접 조작할 수는 없습니다(GUI 도구가 있지만, 본질적으로도 명령어로 변환하는 것입니다). 데이터베이스에 작업을 지시하려면 특별한 언어가 필요합니다.

이 언어가 바로 SQL(Structured Query Language, 구조적 질의어)입니다.

좋은 소식은 SQL이 자연 영어와 매우 비슷해서 읽으면 말하는 것 같다는 것입니다.

3.1 SQL의 핵심 조작: CRUD

대부분의 경우 네 가지 조작만 마스터하면 됩니다. 업계에서 CRUD라고 부릅니다:

조작영어SQL 키워드이해하기
Create생성INSERT새로운 데이터 추가
Read읽기SELECT데이터 조회
Update수정UPDATE데이터 갱신
Delete삭제DELETE데이터 삭제

📊 표에서 알 수 있는 것

이 네 가지 조작은 데이터 처리의 모든 시나리오를 커버합니다:

  • Create: 사용자가 가입할 때, 새 사용자 기록 삽입
  • Read: 사용자가 로그인할 때, 사용자명과 비밀번호 조회
  • Update: 사용자가 프로필을 수정할 때, 테이블의 데이터 갱신
  • Delete: 사용자가 계정을 탈퇴할 때, 사용자 데이터 삭제

이 네 가지만 기억하면 일상 SQL 조작의 80%를 마스터하는 것입니다.

3.2 데이터 조회(SELECT): 데이터베이스에서 가장 많이 사용하는 조작

조회는 데이터베이스에서 가장 중요한 기능이자, 성능 최적화의 핵심입니다.

예시 1: 서울에 있는 모든 사용자 찾기

sql
SELECT name, age FROM users WHERE city = '서울';

단어별 이해:

  • SELECT name, age: name과 age 두 열을 선택
  • FROM users: users 테이블에서
  • WHERE city = '서울': city가 "서울"인 조건에서

반환 결과:

nameage
홍길동25
강감찬28

예시 2: 가격이 5000에서 15000 사이인 상품 찾기

sql
SELECT name, price FROM products
WHERE price BETWEEN 5000 AND 15000;

예시 3: 퍼지 검색(이름에 "홍"이 포함된 사용자 찾기)

sql
SELECT name FROM users WHERE name LIKE '%홍%';

⚠️ 성능 함정: LIKE 사용

LIKE '%홍%'전체 테이블 스캔을 유발하여, 데이터가 많을 때 매우 느립니다.

최적화 제안:

  • LIKE '%홍%' 사용하지 마세요(앞뒤 모두 %)
  • LIKE '홍%'은 사용 가능(뒤에만 %)

LIKE '홍%'은 인덱스를 활용할 수 있지만, LIKE '%홍%'은 인덱스를 사용할 수 없기 때문입니다.

3.3 데이터 삽입(INSERT): 새로운 기록 추가

예시: 새 사용자 추가

sql
INSERT INTO users (user_id, name, age, city, email)
VALUES (1004, '박지성', 35, '광주', 'park@example.com');

단어별 이해:

  • INSERT INTO users: users 테이블에 삽입
  • (user_id, name, age, city, email): 삽입할 열 지정
  • VALUES (1004, '박지성', ...): 해당 값

배치 삽입(더 효율적):

sql
INSERT INTO users (name, age, city) VALUES
('철수', 25, '서울'),
('영희', 28, '부산'),
('민수', 30, '광주');

3.4 데이터 수정(UPDATE): 기록 갱신

예시: 서울에 있는 모든 사용자의 나이를 1씩 증가

sql
UPDATE users SET age = age + 1 WHERE city = '서울';

❌ 매우 위험: WHERE를 잊지 마세요!

WHERE 절을 빼먹으면 모든 행이 수정됩니다!

sql
-- 위험! 모든 사용자의 나이가 26으로 변경됨
UPDATE users SET age = 26;

-- 올바름: user_id = 1001인 사용자만 수정
UPDATE users SET age = 26 WHERE user_id = 1001;

실제 교훈: 2012년, 한 유명 기업의 엔지니어가 WHERE를 잊어서 프로덕션 환경의 수백만 건의 사용자 데이터가 잘못 수정되었고, 시스템이 4시간 동안 마비되어 막대한 손실이 발생했습니다.

3.5 데이터 삭제(DELETE): 기록 삭제

예시: user_id = 1004인 사용자 삭제

sql
DELETE FROM users WHERE user_id = 1004;

❌ 이중 위험: DELETE에는 더더욱 WHERE가 필요!

sql
-- 위험! 테이블의 모든 데이터가 삭제됨!
DELETE FROM users;

-- 올바름: 지정된 행만 삭제
DELETE FROM users WHERE user_id = 1004;

모범 사례:

  1. 삭제하기 전에 SELECT로 먼저 데이터 확인
  2. 중요 시스템에서는 "소프트 삭제" 사용(is_deleted 필드를 추가하여 삭제 표시)
  3. 프로덕션 환경에서 조작 전 데이터 백업

3.6 다중 테이블 쿼리(JOIN): 데이터베이스의 마법의 순간

앞서 설명한 "외래키"가 기억나시나요? SQL의 가장 강력한 점은 연관된 여러 테이블을 한 번에 조회할 수 있다는 것입니다.

시나리오: "홍길동이 구매한 모든 상품" 조회

세 개의 테이블이 있다고 가정합니다:

사용자 테이블(users):

user_idname
1001홍길동

상품 테이블(products):

product_idnameprice
201iPhone 155999
202MacBook14999

주문 테이블(orders):

order_iduser_idproduct_idquantity
500110012011
500210012022

SQL 쿼리:

sql
SELECT u.name, p.name AS product_name, p.price, o.quantity
FROM orders o
JOIN users u ON o.user_id = u.user_id
JOIN products p ON o.product_id = p.product_id
WHERE u.name = '홍길동';

반환 결과:

nameproduct_namepricequantity
홍길동iPhone 1559991
홍길동MacBook149992

JOIN의 과정 이해:

  1. FROM orders o: 주문 테이블에서 시작
  2. JOIN users u ON o.user_id = u.user_id: user_id로 사용자 테이블 연결
  3. JOIN products p ON o.product_id = p.product_id: product_id로 상품 테이블 연결
  4. WHERE u.name = '홍길동': 홍길동의 주문만 필터링
💻SQL 练习场体验 SQL 的 CRUD 操作
SQL 就像和数据库对话:你说"给我找所有年龄大于 25 的用户",数据库就会执行查询并返回结果。即使不会编程,也能很快上手。
📝 示例 SQL
SELECT name, age FROM users WHERE age > 25;
💡 逐词翻译
SELECT name, age选择 name 和 age 这两列
FROM users从 users 这张表
WHERE age > 25在 age 大于 25 的条件下
📊 返回结果
name
age
李四
30
王五
28
🎯核心概念:CRUD 涵盖了所有数据管理的基本需求。无论是淘宝、微信、抖音,它们的数据库操作本质上就是这四种:增、删、改、查。

4. 왜 데이터베이스는 이렇게 빠른가? 인덱스 원리 공개

이것은 데이터베이스에서 가장 신비로운 부분이자, 면접에서 가장 많이 나오는 질문입니다.

Excel에서 "성이 홍인 사람 모두"를 찾으려면 Excel은 첫 행부터 마지막 행까지 스캔해야 합니다. 이것이 바로 전체 테이블 스캔입니다 — 데이터가 많을수록 느려집니다.

하지만 데이터베이스에서는 10억 행의 데이터가 있어도 조회에 몇 밀리초밖에 걸리지 않습니다.

비결은 바로 인덱스(Index)입니다.

4.1 직관적 이해: 사전의 시사점

목차 없는 1000페이지짜리 책에서 단어를 찾아야 한다고 상상해 보세요. 어떻게 해야 할까요?

한 페이지 한 페이지 넘기는 수밖에 없습니다 — 이것이 전체 테이블 스캔으로, 평균 500페이지를 넘겨야 합니다.

하지만 이 책에 음절 인덱스가 있다면?

"데이터베이스"라는 단어를 찾을 때:

  1. 인덱스를 펼쳐 "데"로 시작하는 영역을 찾음
  2. "데" 영역 안에서 "이"를 찾음
  3. 인덱스가 알려줍니다: 256페이지에 있습니다

3번만 넘기면 찾을 수 있습니다! 이것이 인덱스 검색입니다.

데이터베이스의 인덱스는 책의 목차와 같습니다:

  • 인덱스 없음: 행 단위 스캔(10억 행 = 수분)
  • 인덱스 있음: 직접 이동(10억 행 = 3번의 디스크 I/O = 몇 밀리초)

4.2 전체 테이블 스캔 vs 인덱스 검색: 속도 비교

사용자 테이블에 1000만 건의 기록이 있다고 가정합니다.

시나리오: user_id = 5,555,555인 사용자 찾기

방식과정검사해야 할 행 수소요 시간 추정
전체 테이블 스캔첫 행부터 시작하여 한 행씩 확인평균 500만 행5-30초
인덱스 검색인덱스 트리를 검색하여 대상 위치로 직접 이동3-4번의 비교0.003초

속도 차이: 수천 배!

💡 핵심 교훈

인덱스는 은총알이 아닙니다. 대가가 있습니다:

  • 공간 차지: 인덱스에 추가 저장 공간 필요
  • 쓰기 속도 저하: 매번 INSERT/UPDATE/DELETE 시 인덱스도 갱신해야 함

언제 인덱스를 만들어야 하나?

  • 자주 쿼리에 사용되는 열(WHERE, JOIN의 조건)
  • 데이터량이 큰 경우(수천 건 이하는 불필요)

언제 인덱스를 만들지 말아야 하나?

  • 거의 쿼리하지 않는 열
  • 잦은 업데이트가 발생하는 열
  • 데이터량이 적은 테이블

4.3 기반 데이터 구조: B+ 트리

실제 인덱스는 단순한 "알파벳 목록"이 아니라 정교하게 설계된 데이터 구조인 B+ 트리(B+ Tree)입니다.

📖 B+ 트리란?

B+ 트리는 "키가 크고 폭이 넓은" 트리 형태의 데이터 구조입니다:

  • 키가 큼(낮음): 루트에서 잎까지 보통 3-4단계
  • 폭이 넓음: 각 노드가 수백 개의 키값을 저장할 수 있음

왜 "키가 크고 폭이 넓어야" 하나?

데이터는 디스크에 저장되며, 디스크를 읽을 때마다(I/O) 매우 느립니다(메모리보다 수천 배 느림). B+ 트리의 설계 목표는 디스크 I/O 횟수를 최소화하는 것입니다.

  • 3-4단계 높이 = 최대 3-4번의 디스크 읽기
  • 각 단계에 대량의 데이터 저장 = 트리가 너무 높아지지 않음을 보장

실제 예시:

B+ 트리의 각 노드가 1000개의 키값을 저장할 수 있다고 가정:

  • 루트 노드: 1000개의 키값 → 1000개의 자식 노드를 가리킴
  • 중간 노드: 각각 1000개의 키값 저장 → 1000개의 잎 노드를 가리킴
  • 잎 노드: 각각 1000건의 실제 데이터 저장

총 데이터량 = 1000 × 1000 × 1000 = 10억 건의 데이터

트리의 높이 = 3단계

이것은 10억 건의 데이터에서 아무거나 하나를 찾을 때 단 3번의 디스크 I/O만 필요하다는 뜻입니다!

이것이 데이터베이스 쿼리가 엄청나게 빠른 비밀입니다.

🌳B+ 树索引演示理解数据库如何快速查找数据
想象你要在字典里找一个字。你会先看目录,定位到首字母的区域,再在这个区域里找具体页码。B+ 树就是这样的多层目录,让数据库在 10 亿条数据中 3 次就能找到目标。
🐢全表扫描
001用户1
002用户2
003用户3
004用户4
005用户5
006用户6
007用户7
008用户8
009用户9
010用户10
011用户11
012用户12
013用户13
014用户14
015用户15
016用户16
017用户17
018用户18
019用户19
020用户20

👆 点击"开始查找"看全表扫描有多慢

索引查找
根节点
1-100
中间节点
1-10
叶子节点
1
2
3
4
5
6
7
8
9
10

👆 点击"开始查找"看索引有多快

数据量
100 万条
全表扫描
平均 50 万次比较
B+ 树索引
仅 3 次比较
速度提升
10 万倍+
💡核心原理:B+ 树通过"矮胖"的设计,让树的高度只有 3-4 层。每层可以存储成百上千个键值,所以 10 亿数据也只需要 3 次磁盘 I/O。这就是数据库查询飞快的秘密。

5. 트랜잭션: 어떻게 데이터를 잃지 않고, 엉키지 않게 할 것인가?

명절 기차표 예매 상황을 상상해 보세요:

  • 시간 T1: 사용자 A가 조회, "G1234 열차 잔여 좌석 1석" 발견
  • 시간 T2: 사용자 B도 조회, 역시 "잔여 좌석 1석" 발견
  • 시간 T3: 사용자 A가 "구매" 클릭, 시스템이 재고 차감, 표를 A에게 판매
  • 시간 T4: 사용자 B가 "구매" 클릭 — 보호 메커니즘이 없다면, 시스템이 다시 재고를 차감하여 같은 좌석을 B에게도 판매!

이것이 전형적인 동시성 충돌 문제입니다.

5.1 트랜잭션(Transaction)이란?

트랜잭션은 데이터베이스의 한 그룹 조작으로, 이 조작들은 모두 성공하거나 모두 실패하며, "절반만 실행된" 상태가 되지 않습니다.

🤖 일상의 예시

은행 이체가 전형적인 트랜잭션입니다:

  1. 계좌 A에서 100위안 차감
  2. 계좌 B에 100위안 추가

1단계가 성공했는데 2단계가 실패하면(예: 정전) 어떻게 될까요?

  • 트랜잭션 없음: 계좌 A의 돈은 사라지고 계좌 B는 돈을 받지 못해, 돈이 허공에서 증발
  • 트랜잭션 있음: 시스템이 2단계 실패를 감지하고 자동으로 1단계를 롤백, 두 계좌 모두 원래 상태로 복원

이것이 트랜잭션의 원자성입니다: 모두 하거나 모두 하지 않거나.

5.2 트랜잭션의 네 가지 특성(ACID)

트랜잭션에는 네 가지 특성이 있으며, 약자로 ACID라고 합니다:

특성영어의미은행 이체의 예시
Atomicity원자성모두 하거나 모두 하지 않거나차감과 입금이 동시에 성공해야 함, 차감만 하고 입금하지 않을 수 없음
Consistency일관성데이터가 항상 합법적 상태를 유지이체 전후 두 계좌의 총액은 변하지 않아야 함
Isolation격리성여러 트랜잭션이 서로 영향을 미치지 않음A가 이체하는 동안 B가 보는 것은 "이체 전" 또는 "이체 후"의 잔액이어야 하며, 중간 상태를 볼 수 없음
Durability지속성한 번 커밋되면 데이터가 영구 저장이체 성공 후 정전이 나도 계좌 잔액은 되돌아가지 않음

📊 표에서 알 수 있는 것

이 네 가지 특성이 데이터의 안전성을 보장합니다:

  • 원자성: "절반만 실행" 방지(돈은 차감됐는데 입금 안 됨)
  • 일관성: 비합리적 데이터 방지(이체 후 총액이 변함)
  • 격리성: 동시성 충돌 방지(두 사람이 동시에 같은 데이터를 수정)
  • 지속성: 데이터 유실 방지(커밋 후 정전이 나도 영향 없음)

이러한 보장이 없으면 은행 시스템은 전혀 운영될 수 없습니다.

5.3 트랜잭션의 격리 수준: 안전성과 성능의 균형

이론적으로 트랜잭션은 완전히 격리되어야 합니다. 하지만 완전 격리 = 성능 극도로 저하(대량의 잠금이 필요하여 다른 트랜잭션이 대기만 해야 함).

따라서 데이터베이스는 네 가지 격리 수준을 제공합니다:

격리 수준더티 리드논리퍼블 리드팬텀 리드성능적용 시나리오
커밋되지 않은 읽기가능가능가능가장 빠름거의 사용 안 함(데이터가 틀릴 수 있음)
커밋된 읽기불가능가능가능비교적 빠름일반 비즈니스(Oracle 기본값)
반복 가능한 읽기불가능불가능가능보통은행 이체(MySQL 기본값)
직렬화불가능불가능불가능가장 느림극도로 엄격한 시나리오(거의 사용 안 함)

📖 세 가지 "읽기"란 무엇인가?

  • 더티 리드(Dirty Read): 다른 트랜잭션이 아직 커밋하지 않은 데이터를 읽음(롤백될 수 있어 부정확)
  • 논리퍼블 리드(Non-repeatable Read): 같은 트랜잭션에서 같은 데이터를 두 번 읽었는데 결과가 다름(다른 트랜잭션이 수정함)
  • 팬텀 리드(Phantom Read): 같은 트랜잭션에서 두 번 쿼리했는데 결과 집합의 행 수가 다름(다른 트랜잭션이 데이터를 삽입/삭제함)

비유적 예시(은행 잔액 조회):

  • 더티 리드: 잔액이 1000위안이라고 조회했지만, 상대방 트랜잭션이 롤백되어 실제로는 100위안뿐
  • 논리퍼블 리드: 첫 조회 시 잔액 1000위안, 두 번째 조회 시 800위안으로 변함(차감됨)
  • 팬텀 리드: 첫 조회 시 5건의 거래, 두 번째 조회 시 6건(새 거래 추가됨)
🔒事务 ACID 特性演示理解事务如何保证数据安全
想象银行转账:A 转给 B 100 元。这个操作包含两步:从 A 扣 100,给 B 加 100。如果只扣了钱但没到账,就是灾难。事务保证这两步要么全成功,要么全失败
⚛️
A
原子性
Atomicity
⚖️
C
一致性
Consistency
🔒
I
隔离性
Isolation
💾
D
持久性
Durability
👆 点击上方任意特性,查看详细解释
🎯 12306 抢票场景

场景:用户 A 和 B 同时看到还剩 1 张票,同时点击购买。

没有事务:A 扣库存,B 也扣库存,同一张票卖给了两个人!

有事务(隔离性):A 的操作加锁,B 必须等待。A 买完后,库存变为 0,B 看到的是"已售罄"。

💡核心思想:ACID 四个特性共同保证了数据在高并发环境下的不丢、不乱、不冲突。这就是为什么所有涉及资金、订单的系统都必须使用数据库事务。

6. 성능 최적화: 쿼리를 1000배 빠르게 만드는 실전 팁

이제 인덱스, 트랜잭션 같은 핵심 개념을 이해했습니다. 하지만 실제 프로젝트에서는 다양한 성능 문제에 직면할 수 있습니다.

이 절에서는 바로 적용할 수 있는 최적화 전략을 제공합니다.

6.1 인덱스 사용 시 주의사항 가이드

⚠️ 일반적 오류: 인덱스가 무효화되는 함정

인덱스를 만들었는데도 쿼리가 여전히 느린 경우가 많습니다 — 인덱스가 무효화되었기 때문입니다.

인덱스 무효화의 일반적 원인:

  1. 인덱스 열에 함수 사용
  2. 암시적 타입 변환
  3. LIKE 쿼리가 %로 시작
  4. OR 조건(일부 상황)
  5. 복합 인덱스가 최좌측 접두사 원칙을 충족하지 않음

함정 1: 인덱스 열에 함수 사용

sql
-- ❌ 오류: 인덱스 열에 함수를 사용하면 인덱스를 사용할 수 없음
SELECT * FROM users WHERE YEAR(created_at) = 2024;

-- ✅ 올바름: 범위 쿼리로 변경하면 인덱스 사용 가능
SELECT * FROM users
WHERE created_at >= '2024-01-01' AND created_at < '2025-01-01';

함정 2: 암시적 타입 변환

sql
-- user_id가 int 타입이라고 가정
-- ❌ 오류: 문자열을 전달하면 암시적 변환이 발생하여 인덱스를 사용할 수 없음
SELECT * FROM users WHERE user_id = '123';

-- ✅ 올바름: 해당 타입을 전달
SELECT * FROM users WHERE user_id = 123;

함정 3: LIKE가 %로 시작

sql
-- ❌ 오류: %로 시작하면 인덱스를 사용할 수 없음
SELECT * FROM users WHERE name LIKE '%홍길동%';

-- ✅ 올바름: 고정 접두사로 시작하면 인덱스 사용 가능
SELECT * FROM users WHERE name LIKE '홍길동%';

-- ✅ 또는 전문 인덱스 사용(텍스트 검색에 적합)
SELECT * FROM users WHERE MATCH(name) AGAINST('홍길동');

6.2 SQL 최적화 실전 템플릿

템플릿 1: 페이지네이션 최적화(깊은 페이지 문제)

문제와 해결 방법 보기
sql
-- ❌ 문제: OFFSET이 크면 쿼리가 점점 느려짐
SELECT * FROM orders
ORDER BY created_at DESC
LIMIT 10 OFFSET 1000000;

-- ✅ 최적화 방안 1: 이전 쿼리의 타임스탬프를 커서로 사용
SELECT * FROM orders
WHERE created_at < '2024-01-15 12:00:00'
ORDER BY created_at DESC
LIMIT 10;

-- ✅ 최적화 방안 2: 기본키 범위 쿼리 사용
SELECT * FROM orders
WHERE order_id > 1000000
ORDER BY order_id
LIMIT 10;

템플릿 2: 배치 삽입 최적화

sql
-- ❌ 비효율: 여러 번의 단건 삽입(네트워크 왕복이 여러 번)
INSERT INTO users (name, age) VALUES ('홍길동', 25);
INSERT INTO users (name, age) VALUES ('이순신', 30);
INSERT INTO users (name, age) VALUES ('강감찬', 28);

-- ✅ 효율: 단일 SQL 배치 삽입(네트워크 왕복 한 번만)
INSERT INTO users (name, age) VALUES
('홍길동', 25),
('이순신', 30),
('강감찬', 28);

템플릿 3: SELECT * 피하기

sql
-- ❌ 비효율: 모든 열을 반환(필요 없는 대형 필드 포함)
SELECT * FROM users WHERE user_id = 1;

-- ✅ 효율: 필요한 열만 반환
SELECT user_id, name, email FROM users WHERE user_id = 1;

6.3 높은 동시성 시나리오 대응 전략

시나리오문제해결 방안
핫 데이터특정 행이 빈번하게 읽기/쓰기되어 잠금 경쟁 발생캐시(Redis) 사용 + 읽기/쓰기 분리
플래시 세일순간적인 높은 동시성 재고 차감낙관적 잠금 + 재고 사전 로딩 + 메시지 큐로 피크 분산
슬로우 쿼리복잡한 쿼리가 데이터베이스를 마비시킴인덱스 최적화 + 쿼리 분할 + 읽기/쓰기 분리
커넥션 고갈너무 많은 동시 요청으로 커넥션 풀이 고갈됨커넥션 풀 최적화 + 트래픽 제한 + 서비스 강등

💡 핵심 교훈

성능 최적화의 기본 원칙:

  1. 먼저 측정하고, 나중에 최적화: EXPLAIN으로 쿼리 계획을 분석하여 진짜 병목을 찾기
  2. 인덱스 우선: 성능 문제의 80%는 인덱스 최적화로 해결 가능
  3. 데이터베이스 부하 감소: 캐시를 쓸 수 있으면 캐시를, 비동기로 할 수 있으면 비동기로
  4. 분할 정복: 큰 테이블은 작은 테이블로, 큰 쿼리는 작은 쿼리로 분할
查询优化演示常见错误与正确做法对比
很多时候,查询慢不是因为数据库性能差,而是因为 SQL 写错了。下面这些错误,你可能每天都在犯。
1
在索引列上使用函数
SELECT * FROM users WHERE YEAR(created_at) = 2024;
⚠️ 索引失效,全表扫描
SELECT * FROM users WHERE created_at >= '2024-01-01' AND created_at < '2025-01-01';
💡 可以使用索引,查询速度提升 1000 倍
原理:当对列使用函数时,数据库必须先计算每一行的函数值,无法使用索引。把函数移到等号右边,或用范围查询代替。
2
隐式类型转换
3
LIKE 以 % 开头
4
SELECT * 返回所有列
📝 优化建议清单
为 WHERE、JOIN、ORDER BY 的列创建索引
避免在索引列上使用函数或表达式
用 EXPLAIN 分析查询执行计划
只查询需要的列,避免 SELECT *
批量操作代替单条操作
考虑使用覆盖索引减少回表
🎯核心原则:不要让数据库做"多余的工作"。索引失效、全表扫描、返回不必要的数据,这些都是最常见的性能杀手。写出高效 SQL 的关键,是理解数据库如何执行你的查询

7. 요약과 학습 로드맵

표로 데이터베이스의 핵심 개념을 되돌아보겠습니다:

개념한마디 설명해결하는 문제핵심 포인트
테이블, 행, 열데이터의 구성 방식구조화된 데이터를 어떻게 저장할 것인가테이블 = Excel 워크시트, 행 = 기록, 열 = 필드
기본키각 행의 유일한 식별자특정 행을 정확히 찾는 방법유일, 비어 있지 않음, 불변
외래키테이블을 연결하는 다리서로 다른 테이블의 데이터를 연관시키는 방법다른 테이블의 기본키를 가리킴
SQL데이터베이스와 대화하는 언어데이터를 어떻게 추가/조회/수정/삭제할 것인가SELECT, INSERT, UPDATE, DELETE
인덱스쿼리를 가속하는 데이터 구조데이터를 빠르게 찾는 방법B+ 트리, 디스크 I/O 감소
트랜잭션데이터 안전을 보장하는 메커니즘동시성 충돌과 데이터 유실을 방지하는 방법ACID: 원자성, 일관성, 격리성, 지속성

마지막으로

데이터베이스는 매우 깊고 넓은 주제이며, 이 글은 입문일 뿐입니다. 더 깊이 학습하고 싶다면 다음 경로를 권장합니다:

다음 단계 학습:

  1. 실습: MySQL이나 PostgreSQL을 설치하고, 테이블을 만들고, 데이터를 삽입하고, SQL 쿼리를 작성
  2. ORM 프레임워크: 코드에서 데이터베이스를 사용하는 방법 학습(예: SQLAlchemy, Prisma, TypeORM)
  3. 인덱스 최적화: 복합 인덱스, 커버링 인덱스, 인덱스 푸시다운 등 고급 주제 심층 연구
  4. 트랜잭션 원리: MVCC(다중 버전 동시성 제어), 잠금 메커니즘, 격리 수준 구현 이해
  5. 분산 데이터베이스: 샤딩, 읽기/쓰기 분리, 마스터-슬레이브 복제 등 아키텍처 학습

기억하세요: 이론 + 실습 = 진정한 마스터리.