Step 92. SQL 기초 1 — 데이터베이스와 대화하기 (CRUD)
Level 1 — 프로그래밍과 컴퓨터 내부 | 난이도 ★★☆☆☆ | 예상 소요 시간 2.5시간
전제: 터미널 기본 조작(Step 18~20)을 마쳤다.
- 준비물: sqlite3. 리눅스는
sudo apt install sqlite3 -y, macOS는brew install sqlite3, Windows는 WSL을 쓰거나 sqlite.org의 미리 빌드된 도구를 받습니다. 이 챕터의 실측은 WSL(Ubuntu 24.04)의 sqlite3 3.45.1에서 수행했습니다. - 주의: 오늘 실습은 100% 안전합니다. 전부 내 컴퓨터 안의 연습용 파일에서 이루어집니다. SQL은 영어 문장처럼 생긴 명령어입니다 — 암기보다 "읽히는가"가 먼저입니다.
웹 서비스의 겉모습은 HTML과 CSS지만, 그 속살 — 회원 목록, 게시글, 댓글, 그리고 비밀번호 — 은 거의 전부 데이터베이스(DB)라는 창고에 삽니다. 그리고 그 창고지기와 대화하는 언어가 SQL입니다. 보안을 공부하는 여러분에게 SQL이 중요한 이유는 단 하나로 수렴합니다: SQL Injection — 사용자의 입력이 SQL 문장에 끼어들어 명령을 바꿔 버리는 공격 — 이 웹 해킹의 대표선수인데, SQL을 모르면 이 공격은 시작도 안 됩니다. 방어든 공격이든 "정상적인 SQL이 어떻게 생겼는가"를 아는 사람만이 "비정상"을 알아봅니다. 오늘은 그 정상을 손에 익힙니다.
1. 학습 목표
이 챕터를 끝내면 다음을 할 수 있습니다:
- sqlite3에 진입해 표(테이블)를 만들고 나올 수 있다
- CRUD 네 문장(CREATE/INSERT·SELECT·UPDATE·DELETE)을 책 없이 칠 수 있다
- WHERE, LIKE, ORDER BY, LIMIT으로 원하는 행만 골라낼 수 있다
- 점 명령어(
.tables,.schema,.exit)로 낯선 DB 파일을 30초 안에 파악한다 - "WHERE 없는 UPDATE/DELETE"와 "따옴표 충돌"이 왜 위험한지 설명할 수 있다
2. 배경 지식 — 오늘의 도구와 개념
오늘의 도구 한눈에 보기
| 구분 | 내용 |
|---|---|
| 언어·환경 | sqlite3 명령줄 도구 (WSL/리눅스/macOS/Windows 공통) |
| 오늘의 명령 | CREATE TABLE, INSERT INTO, SELECT, UPDATE, DELETE, WHERE, LIKE, ORDER BY, LIMIT, 점 명령(.tables·.schema·.exit) |
| 필요한 개념 | 테이블·행(row)·컬럼(column), 기본키(PRIMARY KEY), 문자열의 작은따옴표, 세미콜론 |
| 오늘의 산출물 | 미니 게시판 창고 board.db와 위키 정리 문서 |
2-1. 관계형 데이터베이스 — 표의 세계
오늘 다룰 종류는 관계형(relational) 데이터베이스로, 데이터를 엑셀 같은 표로 저장합니다. 용어 세 개면 충분합니다.
- 테이블(table) — 표 한 장. 예:
users(회원 표) - 행(row) — 표의 가로 한 줄, 데이터 한 건. 예: 회원 한 명
- 컬럼(column) — 표의 세로 한 칸의 종류, 속성. 예: 이름, 비밀번호
"users 테이블에 행을 하나 추가한다"는 "회원 한 명을 등록한다"와 같은 말입니다. 실제 서비스의 회원가입 버튼 뒤에서 일어나는 일이 바로 그것입니다.
2-2. SQLite — 서버도 없는 DB
데이터베이스 세계에는 거대한 선수들(MySQL, PostgreSQL)이 있지만, 오늘은 SQLite를 씁니다. 서버 설치·설정이 없고 DB 전체가 파일 하나(test.db)입니다. 파이썬에 기본 내장이라 다음 챕터의 파이썬 연동이 그대로 이어지고, 문법은 표준 SQL이라 여기서 배운 것이 다른 DB에서도 거의 그대로 통합니다. 휴대폰 안의 수많은 앱이 실제로 SQLite로 데이터를 저장합니다 — 가볍다고 얕본다면 오산인 선수입니다.
2-3. SQL 읽는 법 — 동사가 먼저다
SQL 문장은 대부분 "동사 + 대상 + 조건"의 영어 문장입니다.
SELECT name FROM users WHERE is_admin = 1;
읽어 보면 — "선택하라(SELECT) name을, 어디서(FROM) users에서, 조건은(WHERE) is_admin이 1인 것". 소리 내어 읽히는 문장이면 이미 반은 이해한 것입니다. 끝의 세미콜론(;)은 "문장 끝"의 마침표로, sqlite3에서는 빠뜨리면 실행이 안 되고 계속 입력을 기다립니다 (3-6에서 실제로 보게 됩니다).
3. 따라 하기
3-1. 설치와 진입
입력
sudo apt install sqlite3 -y # Ubuntu/Debian 계열
sqlite3 test.db
출력 (2026-09-09 실측, sqlite3 3.45.1):
SQLite version 3.45.1 2024-01-30 16:01:20
Enter ".help" for usage hints.
sqlite>
읽는 법: 프롬프트가 sqlite>로 바뀌었습니다. 이제 치는 것은 전부 SQL 또는 SQLite 전용 명령입니다. test.db는 "이 파일을 창고로 쓰겠다"는 선언이며, 파일은 첫 저장 시점에 만들어집니다.
왜: 지금 이 프롬프트가 DB와의 직통 전화입니다. 서비스의 뒷문을 직접 잡아 보는 첫 경험입니다.
3-2. 테이블 만들기 — CREATE
입력
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT,
pw TEXT,
is_admin INTEGER
);
출력 (2026-09-09 실측): 아무 출력이 없으면 성공입니다. .tables를 치면 users가 보입니다.
읽는 법: "users라는 표를 만들어라. 칸은 네 개 — id(정수, 기본키), name(글자), pw(글자), is_admin(정수)." 기본키(PRIMARY KEY)는 각 행의 주민등록번호로, 자동으로 번호가 매겨집니다. .tables는 창고 안의 표 목록을 보여 주는 SQLite 전용 명령(점으로 시작)입니다.
왜: 표의 설계도를 먼저 정하는 것이 관계형 DB의 규칙입니다. 지금은 네 칸짜리지만, 실제 서비스의 회원 표도 구조는 같습니다.
3-3. 데이터 넣기 — INSERT
입력
INSERT INTO users (name, pw, is_admin) VALUES ('admin', 'secret123', 1);
INSERT INTO users (name, pw, is_admin) VALUES ('alice', 'wonderland', 0);
INSERT INTO users (name, pw, is_admin) VALUES ('bob', 'builder99', 0);
INSERT INTO users (name, pw, is_admin) VALUES ('charlie', 'choco789', 0);
INSERT INTO users (name, pw, is_admin) VALUES ('dave', 'davepass', 0);
출력 (2026-09-09 실측): 출력 없이 조용히 성공합니다.
읽는 법: "users에 넣어라(INSERT INTO), 이 칸들에, 이 값들을." 글자(문자열)는 작은따옴표로 감싸는 것이 SQL의 철칙입니다. 따옴표를 빼면 오류가 나거나 다른 뜻이 됩니다.
왜: 회원가입의 실체입니다. 여러분이 지금 손으로 친 이 문장을, 웹 서비스에서는 프로그램이 대신 쳐 줍니다.
3-4. 조회하기 — SELECT와 WHERE
입력
SELECT * FROM users;
출력 (2026-09-09 실측):
1|admin|secret123|1
2|alice|wonderland|0
3|bob|builder99|0
4|charlie|choco789|0
5|dave|davepass|0
읽는 법: *는 "모든 칸"이라는 뜻입니다. 출력의 세로줄(|)은 칸 구분입니다. 회원 다섯 명이 전부 보입니다.
조건을 붙여 봅시다.
SELECT name FROM users WHERE is_admin = 1;
출력 (2026-09-09 실측): admin
읽는 법: "name만, users에서, 관리자인 것만." WHERE가 필터입니다. 이 조합 — 어떤 칸을, 어느 표에서, 어떤 조건으로 — 이 SELECT의 전부입니다.
왜: 로그인의 실체가 바로 이 문장입니다. "이 아이디와 비밀번호를 가진 행이 있느냐"를 SELECT로 묻는 것. 그 문장에 입력이 끼어드는 순간이 곧 SQL Injection이니, 정상의 모습을 지금 눈에 새겨 두세요.
3-5. 수정과 삭제 — UPDATE, DELETE
입력
UPDATE users SET pw='newpass456' WHERE name='admin';
SELECT name, pw FROM users WHERE name='admin';
출력 (2026-09-09 실측): admin|newpass456 — 비밀번호가 바뀌었습니다.
입력
DELETE FROM users WHERE id=5;
SELECT * FROM users;
출력 (2026-09-09 실측): dave가 사라지고 네 명만 남습니다.
1|admin|newpass456|1
2|alice|wonderland|0
3|bob|builder99|0
4|charlie|choco789|0
읽는 법: "고쳐라(UPDATE) users를, SET 뒤처럼, 조건은 WHERE" / "지워라(DELETE FROM) users에서, 조건에 맞는 것을."
⚠️ 치명적 주의: WHERE를 빠뜨리면 전원에게 적용됩니다. 연습 파일의 복사본에서 실제로 확인했습니다 (2026-09-09 실측):
UPDATE users SET pw='x';를 실행하니 모든 회원의 pw가x로 바뀌었습니다.DELETE FROM users;는 표를 텅 비웁니다. 실무의 대형 사고 상당수가 "WHERE 까먹음"입니다 — 실행 전에 조건을 두 번 확인하세요.
왜: CRUD의 나머지 둘입니다. 네 가지를 모두 손에 익혔다면, 여러분은 이미 "DB와 대화할 줄 아는 사람"입니다.
3-6. 예측해 보기 — 따옴표가 깨지면?
예측: 이름에 작은따옴표가 들어간 사람을 등록한다면 어떻게 될까요?
INSERT INTO users (name, pw, is_admin) VALUES ('o'neil', 'pass', 0);
- (a) 정상 등록된다
- (b) 오류가 난다
- (c) 이름이
o로 잘려 등록된다
직접 확인 — 출력 (2026-09-09 실측):
Error: in prepare, near "neil": syntax error
INSERT INTO users (name, pw, is_admin) VALUES ('o'neil', 'pass', 0);
error here ---^
정답은 (b) 입니다. 첫 번째 따옴표에서 문자열이 끝났다고 해석했는데, 뒤에 neil이라는 이해 불가능한 잔해가 남았기 때문입니다. sqlite3가 오류 위치에 화살표(---^)까지 그려 줍니다.
왜 중요한가: 방금 본 것이 "사용자 입력이 SQL 문장의 구조를 깨뜨릴 수 있다"는 현상입니다. 오류가 나면 다행이고 — 만약 이 따옴표가 문장을 깨는 게 아니라 바꾸는 쪽으로 쓰이면? 그것이 SQL Injection의 씨앗입니다. 오늘은 "깨진다"까지만 보고, "바꾼다"는 Step 93에서 눈으로 확인합니다.
3-7. 점 명령어 — SQLite 운전 설명서
SQL이 "창고지기에게 내리는 명령"이라면, 점으로 시작하는 명령은 "SQLite 프로그램 자체의 조작 버튼"입니다.
입력
.schema users
.headers on
.mode column
SELECT * FROM users;
.exit
출력 (2026-09-09 실측): .schema users는 표의 설계도를 보여 줍니다.
CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, pw TEXT, is_admin INTEGER);
.headers on과 .mode column 뒤의 SELECT는 칸 이름이 붙은 반듯한 표로 출력됩니다.
id name pw is_admin
-- ------- ---------- --------
1 admin newpass456 1
2 alice wonderland 0
3 bob builder99 0
4 charlie choco789 0
읽는 법: 자주 쓸 것은 .tables(표 목록), .schema 표이름(구조), .help(전체 목록), .exit(종료) 네 개입니다.
왜: 낯선 DB 파일을 받았을 때 — 예: 침해 분석에서 압수한 서버의 DB — 제일 먼저 하는 일이 .tables와 .schema입니다. "무엇이 들었는가"를 30초 안에 파악하는 기술입니다.
3-8. 조회 응용 — LIKE, ORDER BY, LIMIT
SELECT는 생각보다 많은 질문에 답합니다. 자주 쓰는 세 가지를 더 익혀 봅시다.
입력
SELECT * FROM users WHERE name LIKE 'a%';
SELECT name FROM users ORDER BY name;
SELECT name FROM users ORDER BY id DESC LIMIT 2;
출력 (2026-09-09 실측):
1|admin|newpass456|1 ← LIKE 'a%' : a로 시작하는 회원
2|alice|wonderland|0
admin ← ORDER BY name : 이름순
alice
bob
charlie
charlie ← ORDER BY id DESC LIMIT 2 : 최근 2명
bob
읽는 법: LIKE 'a%'에서 %는 "뒤에 뭐가 와도 상관없다"는 와일드카드입니다. ORDER BY 칼럼은 그 칸 기준 정렬이며 DESC를 붙이면 큰 순서(내림차순), LIMIT N은 앞에서 N개만 가져옵니다.
왜: "관리자만", "최신 글 10개만", "이름에 이 글자가 들어간 것만" — 서비스의 조회 화면 뒤에는 거의 전부 이 문법들이 있습니다. 검색창의 실체가 LIKE라는 것을 알면 웹을 보는 눈이 바뀝니다.
4. 미션과 연습문제
미션 — 미니 게시판 창고 설계와 운영
board.db를 만들고posts테이블을 설계합니다 (칸: id, 작성자, 제목, 내용, 작성일 중 4개 이상).- 글 5개를 INSERT합니다.
- SELECT로 ① 전체 조회 ② 특정 작성자만 조회 ③ 제목만 조회 — 3종을 실행합니다.
- 글 하나를 UPDATE로 수정하고, 하나를 DELETE로 삭제한 뒤 결과를 확인합니다.
- 제목에 ‘공지’가 들어간 글만 LIKE로 검색하고, 최신 글 3개만 ORDER BY + LIMIT으로 뽑습니다.
.schema posts출력을 위키의SQL기초.md에 붙이고, "WHERE 없는 UPDATE가 왜 위험한가"를 두 줄로 적습니다.
연습문제
문제 1. 테이블, 행, 컬럼의 관계를 "회원 표"를 예로 설명해 보세요.
문제 2. SQL 문장 끝의 세미콜론을 빠뜨리면 sqlite3에서 무슨 일이 일어나나요?
문제 3. SELECT * FROM users WHERE name LIKE 'a%';와 WHERE name LIKE '%a%'의 차이를 설명해 보세요.
문제 4. 한 명만 고치려고 UPDATE를 쳤는데 전원이 바뀌었습니다. 원인은 무엇이고, 실행 전에 어떤 습관으로 막을 수 있나요?
5. 모범 답안과 완료 기준
미션 모범 답안
CREATE TABLE posts (
id INTEGER PRIMARY KEY,
author TEXT,
title TEXT,
body TEXT,
created TEXT
);
INSERT INTO posts (author, title, body, created) VALUES
('admin', '공지: 게시판 규칙', '규칙을 지킵시다', '2026-09-09');
INSERT INTO posts (author, title, body, created) VALUES
('alice', '첫 글', '안녕하세요', '2026-09-09');
-- (총 5개 채우기)
SELECT * FROM posts; -- ① 전체
SELECT * FROM posts WHERE author='alice'; -- ② 특정 작성자
SELECT title FROM posts; -- ③ 제목만
UPDATE posts SET body='수정된 내용' WHERE id=2;
DELETE FROM posts WHERE id=5;
SELECT title FROM posts WHERE title LIKE '%공지%';
SELECT title FROM posts ORDER BY id DESC LIMIT 3;
검증하는 법: ① .tables에 posts가 있고 ② UPDATE·DELETE 후 SELECT * 결과가 의도대로인가. ③ LIKE 검색과 최신 3개 추출이 실제로 돌아갔는가. ④ 위키에 .schema posts 출력과 위험 설명 두 줄이 있는가. 전부 ‘예’이면 완성입니다.
연습문제 해답
문제 1 해답. 테이블은 표 한 장(users = 회원 표), 행은 가로 한 줄로 데이터 한 건(회원 한 명), 컬럼은 세로 칸의 종류(name, pw 같은 속성)입니다. "users 테이블에 행을 추가한다" = "회원 한 명을 등록한다"입니다.
문제 2 해답. 문장이 안 끝났다고 보고 들여쓰기된 이어쓰기 프롬프트( ...>)를 띄우며 계속 입력을 기다립니다 (2026-09-09 실측: sqlite> SELECT * FROM users 다음 줄에 ...>가 나타남). ;를 치고 Enter를 누르면 그제서야 실행됩니다.
문제 3 해답. 'a%'는 "a로 시작하는" 이름만 찾고(실측에서 admin, alice가 나옴), '%a%'는 "a가 어디든 들어가는" 이름을 전부 찾습니다. 앞의 %가 "앞에 뭐가 와도 상관없다"를 뜻하기 때문입니다 — dave, charlie도 a를 포함하므로 두 번째 검색에는 걸립니다.
문제 4 해답. 원인은 WHERE 조건 누락(또는 너무 넓은 조건)입니다 — WHERE 없는 UPDATE는 표 전원에게 적용됩니다 (2026-09-09 복사본 실측: 네 명 전원의 pw가 ‘x’가 됨). 예방 습관: UPDATE/DELETE 전에 같은 WHERE로 SELECT를 먼저 돌려 "바뀔 대상"을 눈으로 확인하고, 그대로 동사만 바꿔 실행합니다.
완료 기준 체크리스트
- [ ] sqlite3에 진입해 표를 만들고 나올 수 있다
- [ ] CRUD 네 문장을 책 없이 칠 수 있다
- [ ] WHERE, LIKE, ORDER BY, LIMIT으로 원하는 행만 골라낼 수 있다
- [ ] 점 명령어 4종(
.tables,.schema,.help,.exit)을 쓸 수 있다 - [ ] "WHERE 없는 UPDATE/DELETE"의 위험을 설명할 수 있다
- [ ] 따옴표가 SQL 문법과 충돌하는 현상을 재현하고 설명할 수 있다
- [ ] 미션:
board.db게시판 창고를 완성하고 위키에 정리했다
6. 흔한 실수와 해결
벽 1. 문장을 쳤는데 이상한 프롬프트(...>)가 나와요
증상 (2026-09-09 실측):
sqlite> SELECT * FROM users
...>
원인: 세미콜론(;)을 빠뜨렸습니다. SQLite는 ;를 만날 때까지 문장이 안 끝났다고 봅니다.
해결: ;를 치고 Enter. 습관이 될 때까지 "문장 끝에는 마침표"를 소리 내 외우세요.
벽 2. "no such table: users" 오류가 나요
증상 (2026-09-09 실측):
Error: in prepare, no such table: members
원인: 두 가지가 대부분 — 다른 파일을 열었거나(sqlite3 test2.db), CREATE TABLE을 치다가 오류가 나서 실제로는 안 만들어졌거나.
해결: .tables로 현재 파일의 표 목록을 확인하세요. 빈 목록이면 다시 CREATE하면 됩니다. 어떤 파일을 열었는지부터 확인하는 것이 순서입니다.
벽 3. 따옴표 오류 "syntax error"가 자꾸 나요
증상 (2026-09-09 실측):
Error: in prepare, near "neil": syntax error
원인: 문자열을 따옴표로 안 감쌌거나, 큰따옴표/작은따옴표를 섞어 썼거나, 문자열 안에 따옴표가 있거나(3-6의 상황).
해결: SQL에서 문자열은 작은따옴표가 원칙입니다. 입력값을 눈으로 한 글자씩 훑어 보고, 오류 메시지의 화살표(---^)가 가리키는 위치를 읽으세요.
벽 4. UPDATE/DELETE를 했는데 생각보다 많이 바뀌었어요
증상 (2026-09-09 실측): WHERE 없이 UPDATE users SET pw='x';를 치니 네 명 전원의 비밀번호가 ‘x’로 바뀌었습니다.
원인: WHERE 조건이 넓었거나 아예 빠졌습니다.
해결: 습관으로 해결합니다 — UPDATE/DELETE 전에 같은 WHERE로 SELECT를 먼저 돌려 "바뀔 대상"을 미리 확인. 연습용 파일이니 지금은 다시 만들면 되지만, 이 습관이 실무의 재앙을 막습니다.
벽 5. 결과가 보기 힘들게 붙어 나와요
증상: 1|admin|newpass456|1 같은 세로줄 출력이 읽기 어렵습니다.
원인: sqlite3의 기본 출력 모드가 list이기 때문입니다. 고장이 아닙니다.
해결: .headers on과 .mode column을 치면 칸 이름이 붙은 반듯한 표로 바뀝니다 (3-7절 실측 출력 참조). .exit로 나가면 초기화되니, 매번 켤 때 다시 쳐 주세요.
7. 정리
오늘의 개념
| 개념 | 한 줄 설명 |
|---|---|
| 관계형 데이터베이스 | 데이터를 표(테이블)로 저장하는 창고 |
| 테이블 / 행 / 컬럼 | 표 한 장 / 가로 한 줄(데이터 한 건) / 세로 칸의 종류 |
| 기본키(PRIMARY KEY) | 각 행의 주민등록번호 — 자동 채번, 중복 불가 |
| CRUD | 만들고(Create) 읽고(Read) 고치고(Update) 지우는(Delete) 네 동작 |
| SQLite | 서버 없이 파일 하나로 동작하는 DB — 문법은 표준 SQL |
| SQL Injection의 씨앗 | 입력의 따옴표가 SQL 문장 구조를 깨는 현상 |
오늘의 명령어
| 명령 | 하는 일 |
|---|---|
sqlite3 파일.db |
DB 파일을 열어 대화형 프롬프트 진입 |
CREATE TABLE 이름 (...) |
표 만들기 |
INSERT INTO 표 (칸들) VALUES (값들) |
행 추가 (문자열은 작은따옴표) |
SELECT 칸 FROM 표 WHERE 조건 |
조회 — *는 모든 칸 |
UPDATE 표 SET 칸=값 WHERE 조건 |
수정 (WHERE 필수 확인) |
DELETE FROM 표 WHERE 조건 |
삭제 (WHERE 필수 확인) |
LIKE 'a%' / ORDER BY 칸 DESC / LIMIT N |
부분 검색 / 정렬 / 개수 제한 |
.tables / .schema 표 / .exit |
표 목록 / 설계도 / 종료 |
명령어보다 중요한 감각
SQL은 읽히는 문장입니다. 모르는 문장을 만나면 동사부터 찾아 소리 내어 읽어 보세요 — "선택하라, 어디서, 조건은." 그리고 두 가지 철칙을 몸에 새기세요. 첫째, 문자열은 작은따옴표. 둘째, UPDATE/DELETE는 WHERE 확인이 먼저. 이 둘이 실무 사고의 절반을 막습니다.
오늘 3-6에서 본 따옴표 사고를 기억해 두세요. 입력 하나가 문장을 깨뜨릴 수 있다는 위화감 — 그것이 SQL Injection 이해의 출발점입니다. 오늘은 "깨진다"까지 봤습니다. 이 입력이 문장을 "바꾸는" 쪽으로 쓰일 때 무슨 일이 벌어지는지는, 파이썬 연동과 함께 두 눈으로 직접 확인할 날이 곧 옵니다.
전부 체크되면 Step 92 완료입니다. 사이드바의 체크박스를 눌러 진도를 저장하세요.