SQL 기초: 데이터 조작과 무결성 관리의 표준
여러분이 MySQL 을 쓰든 PostgreSQL 을 쓰든 Oracle 을 쓰든, 데이터베이스에게 말을 걸 때는 거의 똑같은 언어를 씁니다. 바로 SQL(Structured Query Language)이죠. SQL 은 관계형 데이터베이스의…
SQL 기초: 데이터 조작과 무결성 관리의 표준
여러분이 MySQL 을 쓰든 PostgreSQL 을 쓰든 Oracle 을 쓰든, 데이터베이스에게 말을 걸 때는 거의 똑같은 언어를 씁니다. 바로 SQL(Structured Query Language)이죠. SQL 은 관계형 데이터베이스의 공용어입니다. 영어를 배워두면 여러 나라에서 통하듯, SQL 을 익혀두면 어떤 관계형 DB 앞에서도 기죽지 않죠.
그런데 많은 개발자가 SQL 을 그저 SELECT … WHERE … 를 외우는 것으로 끝냅니다. 사실 SQL 에는 그보다 훨씬 넓은 세계가 있습니다. 테이블의 구조를 정의하는 일, 데이터를 넣고 빼는 일, 권한을 통제하는 일, 여러 작업을 하나의 거래로 묶는 일까지요. 이 전체 지도를 이해하면, SQL 은 단순한 조회 도구가 아니라 데이터의 무결성을 지키는 강력한 설계 언어가 됩니다.
이 글에서는 SQL 의 4가지 분류부터 테이블 설계(데이터 타입·제약 조건), CRUD 의 정석, ACID 와 트랜잭션, 그리고 SQL 을 ‘집합’ 으로 바라보는 관점까지 차근차근 짚어보겠습니다. 예시는 MySQL 을 기준으로 합니다.
1. SQL 은 어떤 언어인가
1–1. “어떻게” 가 아니라 “무엇” 을 말하는 선언형 언어
대부분의 프로그래밍 언어(자바, 파이썬 등)는 절차형입니다. “먼저 이걸 하고, 그다음 저걸 반복하고…” 처럼 단계를 일일이 지시하죠. 반면 SQL 은 선언형(Declarative) 언어입니다. “어떻게 가져올지” 가 아니라 “무엇을 원하는지” 만 말합니다.
예를 들어 “나이가 20세 이상인 회원” 을 원할 때, 우리는 디스크의 어느 위치를 어떤 순서로 읽으라고 지시하지 않습니다. 그냥 WHERE age >= 20 이라고 ‘원하는 결과’ 만 적죠. 실제로 데이터를 어떻게 찾을지(인덱스를 탈지, 전체를 훑을지)는 데이터베이스가 알아서 결정합니다. 이 점이 SQL 을 강력하면서도 배우기 쉽게 만듭니다.
1–2. SQL 의 4가지 분류
SQL 명령어는 역할에 따라 크게 네 갈래로 나뉩니다. 건물에 비유하면 이해가 쉽습니다.

출처 : Generated by AI
- DDL(Data Definition Language): CREATE, ALTER, DROP. 테이블이나 구조 자체를 만들고 바꾸고 지웁니다. 건물을 짓고 허무는 일이죠. 한 가지 주의할 점은, DDL 은 실행하는 순간 자동으로 확정(커밋)되어 되돌리기 어렵다는 것입니다.
- DML(Data Manipulation Language): INSERT, SELECT, UPDATE, DELETE. 구조 안에 든 데이터를 넣고 읽고 바꾸고 지웁니다. 우리가 매일 쓰는 바로 그 SQL 이죠. (조회만 따로 떼어 DQL 이라 부르기도 합니다.)
- DCL(Data Control Language): GRANT, REVOKE. 누가 무엇을 할 수 있는지 권한을 주고 회수합니다. 출입증을 발급하고 거두는 일로, 주로 관리자(DBA)의 영역입니다.
- TCL(Transaction Control Language): COMMIT, ROLLBACK, SAVEPOINT. 여러 작업을 하나의 거래로 묶어 통째로 확정하거나 취소합니다. 잠시 뒤 ACID 에서 자세히 다룹니다.
2. 테이블 설계: 데이터 타입과 제약 조건
데이터를 넣기 전에, 먼저 그 데이터를 담을 그릇(테이블)을 만들어야 합니다. 이때 두 가지를 정합니다. 각 칸에 어떤 종류의 값이 들어갈지(데이터 타입)와, 어떤 값은 허용하지 않을지(제약 조건)입니다.
2–1. 데이터 타입: 그릇의 크기를 정하다
데이터 타입은 컬럼에 들어갈 값의 종류와 크기를 정합니다. MySQL 의 대표적인 타입은 이렇습니다.
- 정수: TINYINT(1바이트), INT(4바이트), BIGINT(8바이트). ID 나 개수에는 보통 INT 가 적당하고, 아주 큰 수가 필요할 때만 BIGINT 를 씁니다.
- 소수: 돈처럼 정확해야 하는 값은 DECIMAL 을 씁니다. FLOAT/DOUBLE 은 근사값이라 금액 계산에 쓰면 미세한 오차가 생깁니다.
- 문자열: 길이가 들쭉날쭉하면 VARCHAR, 길이가 항상 고정이면(예: 국가 코드 2자리) CHAR, 아주 긴 글은 TEXT 를 씁니다.
- 날짜·시간: DATE(날짜만), DATETIME, TIMESTAMP 등이 있습니다.
여기서 중요한 원칙이 하나 있습니다.
📌 타입은 필요한 만큼만, 작게 잡아라. 타입이 작을수록 행 하나의 크기가 줄고, 한 페이지에 더 많은 행이 들어가 디스크 I/O 와 캐시 효율이 좋아진다.
습관적으로 모든 정수에 BIGINT 를 박거나, 두 글자면 충분한 코드에 VARCHAR(255) 를 쓰는 것은 작지만 쌓이면 무시할 수 없는 낭비입니다.
2–2. 함정 주의: DATETIME vs TIMESTAMP
날짜·시간 타입에서 초보자가 자주 헷갈리는 함정이 DATETIME 과 TIMESTAMP 의 차이입니다.
- DATETIME: 입력한 값을 그대로 저장합니다. 시간대(타임존) 변환이 없고, 표현 범위가 9999년까지로 넓습니다. 사용자가 입력한 ‘약속 시각’ 같은 값에 적합합니다.
- TIMESTAMP: 내부적으로 UTC 로 저장하고 조회 시 세션 시간대로 변환합니다. 생성·수정 시각을 자동으로 채워주는 기능이 있지만, 표현 범위가 2038년까지라는 제약이 있습니다. created_at, updated_at 같은 기록 추적용으로 흔히 씁니다.
둘을 구분 없이 쓰면 “왜 시간이 9시간 어긋나지?” 같은 타임존 버그로 한참을 헤매게 됩니다.
2–3. 제약 조건: 잘못된 데이터를 입구에서 막다
제약 조건(Constraint)은 “이런 데이터는 애초에 들어올 수 없다” 는 규칙을 테이블에 새겨두는 것입니다. 잘못된 데이터를 입구에서 차단하는 문지기죠.
CREATE TABLE members (
id BIGINT PRIMARY KEY AUTO_INCREMENT, - 고유 식별자
email VARCHAR(255) NOT NULL UNIQUE, - 비어 있을 수 없고, 중복 불가
name VARCHAR(50) NOT NULL, - 비어 있을 수 없음
age INT CHECK (age >= 0), - 음수 나이 금지
grade VARCHAR(10) DEFAULT 'BRONZE', - 기본값
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP - 생성 시각 자동 기록
);
- NOT NULL: 빈 값(NULL)을 허용하지 않습니다.
- UNIQUE: 중복 값을 허용하지 않습니다(예: 이메일).
- DEFAULT: 값을 안 넣으면 기본값이 들어갑니다.
- CHECK: 조건을 만족하는 값만 허용합니다(예: 나이는 0 이상).
이런 규칙을 애플리케이션 코드가 아니라 데이터베이스 자체에 새겨두면, 어떤 경로로 데이터가 들어오든 무결성이 보장됩니다.
💡 제약 조건 중에서도 기본 키(PRIMARY KEY)와 외래 키(FOREIGN KEY), 그리고 이들이 떠받치는 무결성의 종류는 그 자체로 깊은 주제입니다. 관계의 미학: Key 설계와 데이터 무결성에서 이어서 보시길 권합니다.
3. 데이터 조작의 정석: CRUD
이제 그릇에 데이터를 다뤄봅시다. 데이터 조작의 기본은 네 가지, 흔히 CRUD 라고 부릅니다. 생성(Create), 조회(Read), 수정(Update), 삭제(Delete)죠. SQL 로는 INSERT, SELECT, UPDATE, DELETE 가 각각 대응합니다.
- Create: 새 데이터 추가
INSERT INTO members (email, name, age) VALUES ('kim@mail.com', '김철수', 28);
- Read: 데이터 조회
SELECT id, name, age FROM members WHERE age >= 20;
- Update: 기존 데이터 수정
UPDATE members SET grade = 'GOLD' WHERE id = 1;
- Delete: 데이터 삭제
DELETE FROM members WHERE id = 1;
WHERE 없는 UPDATE 와 DELETE 의 공포
CRUD 에서 단 하나의 주의사항만 기억해야 한다면, 바로 이것입니다.
⚠️ UPDATE 와 DELETE 에서 WHERE 절을 빠뜨리면, 테이블의 모든 행이 수정되거나 삭제된다.
DELETE FROM members; 한 줄은 회원 전체를 날려버립니다. 실무에서 가장 악명 높은 사고 중 하나죠. 그래서 수정·삭제 쿼리를 쓸 때는 먼저 같은 조건으로 SELECT 를 돌려 “정말 이 행들이 맞는지” 확인한 뒤 실행하는 습관이 안전합니다.
4. ACID 와 트랜잭션: 전부 아니면 전무
4–1. 트랜잭션이란 무엇인가
은행에서 A 의 계좌에서 B 의 계좌로 10만 원을 이체한다고 해봅시다. 이건 사실 두 개의 작업입니다. ① A 에서 10만 원 출금, ② B 에 10만 원 입금. 그런데 ①만 성공하고 ②가 실패하면? 돈이 허공으로 사라집니다.
이런 사고를 막기 위해, 여러 작업을 하나의 묶음으로 다루는 것이 트랜잭션(Transaction)입니다. 묶음 안의 작업은 전부 성공하거나, 전부 실패해야 합니다. 중간은 없죠.
START TRANSACTION;
UPDATE accounts SET balance = balance - 100000 WHERE id = 'A'; - 출금
UPDATE accounts SET balance = balance + 100000 WHERE id = 'B'; - 입금
COMMIT; - 둘 다 성공해야 확정. 중간에 문제가 생기면 ROLLBACK 으로 전부 취소
4–2. ACID: 트랜잭션의 4대 약속
신뢰할 수 있는 트랜잭션은 네 가지 성질을 보장합니다. 머리글자를 따 ACID 라고 부릅니다.

출처 : Generated by AI
- 원자성(Atomicity): 전부 성공하거나 전부 실패한다. 출금만 되고 입금이 안 되는 어중간한 상태는 없습니다.
- 일관성(Consistency): 트랜잭션 전후로 데이터의 규칙(제약·무결성)이 항상 지켜집니다. 잔고가 음수가 되는 일은 없죠.
- 고립성(Isolation): 여러 트랜잭션이 동시에 실행돼도 서로 간섭하지 않습니다. 마치 혼자 쓰는 것처럼 보이죠.
- 지속성(Durability): 한번 커밋된 데이터는 서버가 죽어도 사라지지 않습니다.
4–3. 트랜잭션을 제어하는 명령: TCL
이 트랜잭션을 직접 다루는 명령이 앞서 본 TCL 입니다. COMMIT 은 묶음의 모든 변경을 영구히 확정하고, ROLLBACK 은 묶음 전체를 없던 일로 되돌립니다. SAVEPOINT 는 트랜잭션 안에 중간 저장 지점을 찍어, 문제가 생겼을 때 그 지점까지만 되돌릴 수 있게 해줍니다. ACID 라는 든든한 약속은 결국 이 세 명령 위에서 작동합니다.
5. 집합론적 사고: SQL 을 ‘집합’ 으로 보기
마지막으로, 초보자와 숙련자를 가르는 사고의 전환 하나를 이야기하겠습니다. SQL 을 잘 쓰는 비결은, 데이터를 ‘한 줄씩’ 이 아니라 ‘집합(덩어리)’ 으로 바라보는 것입니다.
5–1. 한 줄씩 vs 한꺼번에
절차형 언어에 익숙한 개발자는 무심코 이렇게 생각합니다. “회원 목록을 가져와서, 반복문을 돌면서, 한 명씩 포인트를 100점씩 올리자.” 코드로 옮기면 이렇죠.
- Bad: 절차적 사고 - 애플리케이션에서 한 줄씩 반복 처리
for (const user of users) {
UPDATE accounts SET point = point + 100 WHERE user_id = user.id;
}
- → 회원이 100만 명이면 쿼리도 100만 번. 매우 느리다.
- Good: 집합적 사고 - 조건에 맞는 '집합' 을 한 번에 처리
UPDATE accounts
SET point = point + 100
WHERE user_id IN (SELECT id FROM members WHERE grade = 'GOLD');
위쪽은 회원 한 명 한 명을 따로 처리합니다. 회원이 100만 명이면 데이터베이스를 100만 번 왕복하죠. 아래쪽은 “GOLD 등급 회원이라는 집합 전체” 를 단 한 번의 명령으로 처리합니다.
5–2. 왜 집합으로 생각해야 하는가
데이터베이스는 집합 단위 연산에 고도로 최적화되어 있습니다. 한 줄씩 명령을 주고받으면 매번 통신 비용과 처리 비용이 드는 반면, 집합 하나를 통째로 넘기면 데이터베이스가 내부에서 가장 효율적인 방법으로 한꺼번에 처리합니다. 같은 일을 시켜도 집합적 접근이 몇 배씩 빠른 이유죠.
비유 🏢: 100명에게 공지를 전할 때, 한 명씩 전화를 돌리는 것(절차적)과 단체 메시지 한 번을 보내는 것(집합적)의 차이입니다. SQL 을 쓸 때 “반복문을 돌려야 하나?” 라는 생각이 들면, 잠깐 멈춰서 “이걸 하나의 집합 조건으로 표현할 수 없을까?” 를 먼저 자문해 보세요. 그것이 SQL 다운 사고의 출발점입니다.
마무리: SQL 은 조회가 아니라 설계다
지금까지 우리는 SQL 의 뼈대를 훑어보았습니다. 구조를 정의하는 DDL 부터 데이터를 다루는 DML, 권한의 DCL, 트랜잭션의 TCL 이라는 네 갈래의 지도를 그렸고, 테이블을 설계하는 데이터 타입과 제약 조건, CRUD 의 정석과 그 함정, 트랜잭션을 떠받치는 ACID, 그리고 데이터를 집합으로 보는 관점까지 살펴보았습니다.
SQL 을 단순히 데이터를 꺼내는 도구로만 보면, 평생 SELECT 언저리에 머무릅니다. 하지만 SQL 을 데이터의 구조와 무결성을 지키는 설계 언어로 바라보기 시작하면, 같은 문제도 훨씬 견고하고 빠르게 풀어낼 수 있습니다. 제약 조건으로 잘못된 데이터를 입구에서 막고, 트랜잭션으로 데이터를 안전하게 묶고, 집합적으로 사고하는 개발자 — 그것이 데이터를 진짜로 다룰 줄 아는 사람의 모습입니다.
메타데이터
- post_id
- b65c9a2136db
- slug
- sql-기초-데이터-조작과-무결성-관리의-표준-b65c9a2136db
- url
- https://medium.com/@heizence6626/sql-%EA%B8%B0%EC%B4%88-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A1%B0%EC%9E%91%EA%B3%BC-%EB%AC%B4%EA%B2%B0%EC%84%B1-%EA%B4%80%EB%A6%AC%EC%9D%98-%ED%91%9C%EC%A4%80-b65c9a2136db
- canonical_url
- https://medium.com/@heizence6626/sql-%EA%B8%B0%EC%B4%88-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A1%B0%EC%9E%91%EA%B3%BC-%EB%AC%B4%EA%B2%B0%EC%84%B1-%EA%B4%80%EB%A6%AC%EC%9D%98-%ED%91%9C%EC%A4%80-b65c9a2136db
- author_url
- https://medium.com/@heizence6626
- status
- ok
- fetched_at
- 2026-06-29 02:33:43