테이블 수정과 삭제
한 번 만든 테이블이 영원히 그대로 유지되는 일은 없습니다. 서비스가 성장하면 새로운 기능을 위해 컬럼을 추가해야 하고, 요구사항이 바뀌면 데이터 타입을 변경해야 하며, 불필요한 테이블은 정리해야 합니다. 이런 테이블 구조 변경을 담당하는 DDL 명령이 ALTER TABLE, DROP TABLE, TRUNCATE TABLE입니다.
하지만 운영 중인 데이터베이스의 스키마를 변경하는 것은 매우 위험한 작업입니다. 수백만 행이 들어 있는 테이블에 NOT NULL 컬럼을 추가하면 테이블 전체에 락이 걸려 서비스가 멈출 수 있고, 컬럼 타입을 변경하면 기존 데이터가 변환에 실패할 수 있습니다. 그래서 스키마 변경은 문법을 아는 것만큼이나 어떻게 안전하게 변경할 것인가를 아는 것이 중요합니다.
이 절에서는 ALTER TABLE의 다양한 사용법, DROP TABLE과 TRUNCATE TABLE의 차이, 운영 중 스키마 변경의 위험과 안전한 마이그레이션 전략, 그리고 DBMS별 온라인 DDL 지원 현황까지 다룹니다.
ALTER TABLE — 테이블 구조 변경
ALTER TABLE은 이미 생성된 테이블의 구조를 변경하는 DDL 명령입니다. 컬럼 추가, 수정, 삭제, 이름 변경, 제약 조건 관리 등 다양한 작업을 수행할 수 있습니다. DDL의 커밋/롤백 처리는 DBMS마다 다릅니다. Oracle, MySQL처럼 많은 DDL이 암묵적으로 커밋되는 DBMS도 있고, PostgreSQL처럼 트랜잭션 안에서 되돌릴 수 있는 DBMS도 있으므로 운영 변경 전에는 반드시 사용 중인 DBMS의 동작을 확인해야 합니다.
컬럼 추가 (ADD)
-- Oracle: 단일 컬럼
ALTER TABLE employees ADD phone VARCHAR2(20);
-- Oracle: 여러 컬럼은 괄호로 묶음
ALTER TABLE employees ADD (
phone VARCHAR2(20),
address VARCHAR2(200) DEFAULT '미입력'
);
-- MySQL/PostgreSQL: 괄호 불필요, COLUMN 키워드는 선택 가능
ALTER TABLE employees ADD phone VARCHAR(20);
ALTER TABLE employees ADD COLUMN phone VARCHAR(20);
-- SQL Server
ALTER TABLE employees ADD phone NVARCHAR(20);데이터가 있는 테이블에 컬럼을 추가하면, 기존 행의 새 컬럼 값은 NULL(또는 DEFAULT 값)이 됩니다. NOT NULL 컬럼을 추가하려면 기존 행에 들어갈 값을 함께 마련해야 합니다. DEFAULT를 지정하거나, NULL 허용 컬럼으로 먼저 추가한 뒤 값을 채우고 NOT NULL을 적용하는 단계적 방식이 필요합니다.
-- 잘못된 방법 (기존 데이터 때문에 에러)
ALTER TABLE employees ADD grade VARCHAR(10) NOT NULL;
-- ERROR: 기존 행의 grade가 NULL이므로 제약 위반
-- 올바른 방법 1: DEFAULT 값 함께 지정
ALTER TABLE employees ADD grade VARCHAR(10) NOT NULL DEFAULT 'JUNIOR';
-- 올바른 방법 2: 단계적 추가
ALTER TABLE employees ADD grade VARCHAR(10); -- NULL 허용으로 추가
UPDATE employees SET grade = 'JUNIOR' WHERE grade IS NULL; -- 값 채우기
-- Oracle / MySQL
ALTER TABLE employees MODIFY grade VARCHAR(10) NOT NULL; -- NOT NULL 적용
-- PostgreSQL
ALTER TABLE employees ALTER COLUMN grade SET NOT NULL;단계적 추가 방법이 운영 환경에서는 더 안전합니다. DEFAULT 값이 모든 행에 적절하지 않을 수 있고, DBMS/버전에 따라 DEFAULT 컬럼 추가가 메타데이터 변경으로 끝날 수도 있지만 테이블 재작성이나 락이 발생할 수도 있기 때문입니다.
컬럼에 추가할 위치 지정 (MySQL 전용)
MySQL에서는 컬럼을 추가할 위치를 지정할 수 있습니다. Oracle, PostgreSQL, SQL Server에서는 이 기능을 지원하지 않습니다(항상 마지막에 추가됨). 다만 컬럼 순서는 가독성 문제에 가깝고, 쿼리 결과나 저장 성능을 설계하는 핵심 기준으로 삼지는 않습니다.
-- 첫 번째 컬럼으로 추가
ALTER TABLE employees ADD employee_code VARCHAR(20) FIRST;
-- 특정 컬럼 뒤에 추가
ALTER TABLE employees ADD phone VARCHAR(20) AFTER email;컬럼 수정 (MODIFY / ALTER COLUMN)
기존 컬럼의 데이터 타입, 크기, DEFAULT 값, NULL 허용 여부를 변경합니다.
-- Oracle: MODIFY
ALTER TABLE employees MODIFY (name VARCHAR2(200));
ALTER TABLE employees MODIFY (salary NUMBER(12,2) DEFAULT 0);
-- MySQL: MODIFY
ALTER TABLE employees MODIFY name VARCHAR(200);
ALTER TABLE employees MODIFY name VARCHAR(200) NOT NULL;
-- PostgreSQL: ALTER COLUMN
ALTER TABLE employees ALTER COLUMN name TYPE VARCHAR(200);
ALTER TABLE employees ALTER COLUMN salary SET DEFAULT 0;
ALTER TABLE employees ALTER COLUMN name SET NOT NULL;
-- SQL Server: ALTER COLUMN
ALTER TABLE employees ALTER COLUMN name NVARCHAR(200);컬럼 수정 시 주의사항이 있습니다. 크기를 줄이면(VARCHAR(200) → VARCHAR(50)) 기존 데이터가 50자를 초과하는 행이 있다면 에러가 발생합니다. 타입을 변경하면(VARCHAR → INT) 기존 데이터가 숫자가 아닌 문자열을 포함하고 있으면 변환에 실패합니다.
-- 크기 축소 전: 최대 길이 확인
SELECT MAX(LENGTH(name)) FROM employees; -- 결과: 85
-- 85자 이하로는 줄일 수 없음
-- 타입 변경 전: 변환 가능 여부 확인 (MySQL)
SELECT age FROM employees WHERE age NOT REGEXP '^[0-9]+#x27;;
-- PostgreSQL
SELECT age FROM employees WHERE age !~ '^[0-9]+#x27;;
-- Oracle
SELECT age FROM employees WHERE NOT REGEXP_LIKE(age, '^[0-9]+#x27;);
-- 숫자가 아닌 값이 있는지 확인컬럼 삭제 (DROP COLUMN)
-- 대부분의 DBMS에서 지원
ALTER TABLE employees DROP COLUMN phone;
-- Oracle: 여러 컬럼 동시 삭제
ALTER TABLE employees DROP (phone, fax, pager);
-- Oracle: 큰 테이블에서 컬럼 비활성화 (빠름)
ALTER TABLE employees SET UNUSED COLUMN phone;
-- 나중에 실제 삭제
ALTER TABLE employees DROP UNUSED COLUMNS;Oracle의 SET UNUSED는 대용량 테이블에서 유용합니다. DROP COLUMN은 모든 행에서 해당 컬럼의 데이터를 물리적으로 제거하므로 시간이 오래 걸리지만, SET UNUSED는 데이터 딕셔너리에서 컬럼을 논리적으로 숨기기만 하므로 즉시 완료됩니다. 물리적 제거는 나중에 여유 있을 때 수행할 수 있습니다.
컬럼을 삭제하면 해당 컬럼의 데이터는 영구적으로 사라집니다. 또한 그 컬럼을 참조하는 인덱스, 뷰, 트리거, 프로시저 등도 영향을 받습니다.
컬럼 이름 변경 (RENAME COLUMN)
-- Oracle / PostgreSQL
ALTER TABLE employees RENAME COLUMN name TO full_name;
-- MySQL: CHANGE (타입도 함께 명시해야 함)
ALTER TABLE employees CHANGE name full_name VARCHAR(200);
-- MySQL 8.0+: RENAME COLUMN
ALTER TABLE employees RENAME COLUMN name TO full_name;
-- SQL Server: sp_rename 프로시저
EXEC sp_rename 'employees.name', 'full_name', 'COLUMN';MySQL의 CHANGE는 컬럼 이름과 타입을 동시에 변경할 수 있지만, 이름만 바꾸고 싶어도 타입을 반드시 명시해야 합니다. 실수로 타입을 다르게 적으면 의도치 않은 타입 변경이 발생할 수 있으므로 주의해야 합니다.
테이블 이름 변경
-- Oracle / PostgreSQL
ALTER TABLE employees RENAME TO staff;
-- MySQL
RENAME TABLE employees TO staff;
-- Oracle
RENAME employees TO staff;
-- SQL Server
EXEC sp_rename 'employees', 'staff';제약 조건 추가/삭제
-- PRIMARY KEY 추가
ALTER TABLE employees ADD CONSTRAINT pk_emp PRIMARY KEY (emp_id);
-- FOREIGN KEY 추가
ALTER TABLE orders ADD CONSTRAINT fk_orders_user
FOREIGN KEY (user_id) REFERENCES users(user_id);
-- CHECK 제약 추가
ALTER TABLE employees ADD CONSTRAINT ck_age CHECK (age >= 0 AND age <= 150);
-- UNIQUE 제약 추가
ALTER TABLE users ADD CONSTRAINT uq_email UNIQUE (email);
-- NOT NULL 추가 (Oracle)
ALTER TABLE employees MODIFY (name NOT NULL);
-- NOT NULL 추가 (PostgreSQL)
ALTER TABLE employees ALTER COLUMN name SET NOT NULL;-- Oracle / PostgreSQL / SQL Server: 이름으로 삭제
ALTER TABLE employees DROP CONSTRAINT ck_age;
-- MySQL: 외래키는 DROP FOREIGN KEY
ALTER TABLE orders DROP FOREIGN KEY fk_orders_user;
-- MySQL: UNIQUE 제약은 보통 인덱스 이름으로 삭제
ALTER TABLE users DROP INDEX uq_email;
-- MySQL / Oracle: PRIMARY KEY 삭제
ALTER TABLE employees DROP PRIMARY KEY;
-- NOT NULL 해제 (Oracle)
ALTER TABLE employees MODIFY (name NULL);
-- NOT NULL 해제 (PostgreSQL)
ALTER TABLE employees ALTER COLUMN name DROP NOT NULL;제약 조건 비활성화/활성화 (Oracle)
운영 중 대량 데이터 적재 시 제약 조건 검사가 성능을 저하시키므로, 일시적으로 비활성화할 수 있습니다.
-- 비활성화
ALTER TABLE employees DISABLE CONSTRAINT ck_age;
-- 활성화 (기존 데이터 검증 포함)
ALTER TABLE employees ENABLE CONSTRAINT ck_age;
-- 활성화 (기존 데이터 검증 건너뛰기 — 새 데이터만 검사)
ALTER TABLE employees ENABLE NOVALIDATE CONSTRAINT ck_age;ENABLE NOVALIDATE는 기존 데이터가 제약 조건을 위반할 수 있지만, 새로 입력되는 데이터에 대해서만 제약을 적용합니다. 대량 마이그레이션 후 점진적으로 기존 데이터를 정리할 때 유용합니다.
DROP TABLE — 테이블 삭제
DROP TABLE은 테이블 자체를 완전히 제거합니다. 테이블 구조(스키마), 데이터, 인덱스, 트리거, 권한 등 테이블에 속한 모든 것이 삭제됩니다.
-- 기본 삭제
DROP TABLE employees;
-- 테이블이 존재할 때만 삭제 (에러 방지)
DROP TABLE IF EXISTS employees; -- MySQL, PostgreSQL, SQL Server 2016+
-- Oracle은 버전에 따라 IF EXISTS 지원 여부가 다르므로
-- 미지원 환경에서는 PL/SQL 블록으로 처리
BEGIN
EXECUTE IMMEDIATE 'DROP TABLE employees';
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE != -942 THEN -- ORA-00942: table or view does not exist
RAISE;
END IF;
END;외래키 참조가 있는 테이블 삭제
다른 테이블에서 외래키로 참조하고 있는 테이블은 그냥 삭제할 수 없습니다.
-- 에러 발생
DROP TABLE users;
-- ERROR: Cannot drop table 'users' referenced by a foreign key constraint
-- Oracle: CASCADE CONSTRAINTS로 참조하는 FK도 함께 삭제
DROP TABLE users CASCADE CONSTRAINTS;
-- PostgreSQL: CASCADE로 참조하는 FK와 종속 객체 삭제
DROP TABLE users CASCADE;
-- MySQL: FK 검사 비활성화 후 삭제 (최후수단)
SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE users;
SET FOREIGN_KEY_CHECKS = 1;MySQL에서 FOREIGN_KEY_CHECKS를 끄면 참조 무결성 검사를 우회할 수 있지만, 잘못 사용하면 자식 테이블에 고아 데이터가 남습니다. 운영에서는 먼저 참조하는 자식 테이블과 FK를 확인하고, 삭제 순서나 제약 정리 계획을 명시하는 것이 원칙입니다.
CASCADE로 삭제하면 참조하는 외래키 제약만 삭제되고, 참조하는 테이블의 데이터는 삭제되지 않습니다(PostgreSQL의 CASCADE는 종속 객체(뷰 등)도 함께 삭제). 이 점을 혼동하지 않아야 합니다.
Oracle의 Flashback Drop
Oracle 10g부터는 DROP TABLE이 테이블을 완전히 삭제하지 않고 휴지통(Recycle Bin)에 넣습니다.
-- 삭제 (휴지통으로 이동)
DROP TABLE employees;
-- 휴지통 확인
SELECT object_name, original_name FROM recyclebin;
-- 복구
FLASHBACK TABLE employees TO BEFORE DROP;
-- 완전 삭제 (휴지통에 남기지 않음)
DROP TABLE employees PURGE;
-- 휴지통 비우기
PURGE RECYCLEBIN;TRUNCATE TABLE — 데이터 전체 삭제
TRUNCATE TABLE은 테이블의 모든 데이터를 삭제하지만, 테이블 구조는 유지합니다. 일반적으로 DDL 성격이 강해 Oracle, MySQL에서는 사용자 ROLLBACK으로 되돌리기 어렵고, PostgreSQL처럼 트랜잭션 안에서 롤백 가능한 DBMS도 있습니다. 운영에서는 DELETE보다 빠르다는 이유만으로 선택하지 말고 복구 가능성과 참조 관계를 먼저 확인해야 합니다.
-- 기본
TRUNCATE TABLE access_logs;
-- Oracle: 스토리지 반환 옵션
TRUNCATE TABLE access_logs DROP STORAGE; -- 할당된 공간 반환 (기본)
TRUNCATE TABLE access_logs REUSE STORAGE; -- 할당된 공간 유지REUSE STORAGE는 같은 테이블에 곧 다시 데이터를 넣을 예정일 때 유용합니다. 공간을 반환했다가 다시 할당하는 오버헤드를 줄여줍니다.
DELETE vs TRUNCATE vs DROP 비교
| 구분 | DELETE FROM | TRUNCATE TABLE | DROP TABLE |
|---|---|---|---|
| 분류 | DML | DDL 성격 | DDL |
| 동작 | 조건에 맞는 행 삭제 | 모든 행 삭제 | 테이블 자체 삭제 |
| 구조 유지 | 예 | 예 | 아니오 |
| WHERE | 사용 가능 | 불가 | 불가 |
| ROLLBACK | 가능 | DBMS별 차이 | DBMS별 차이 |
| 로그 기록 | 행별로 기록 (Undo + Redo) | DBMS별 최소/메타 중심 | DBMS별 차이 |
| 속도 | 느림 (행 단위 처리) | 매우 빠름 | 빠름 |
| 트리거 발생 | 예 (DELETE 트리거) | DELETE 트리거는 보통 미발생 | 해당 없음 |
| AUTO_INCREMENT | 이어서 증가 | DBMS/옵션별 초기화 | 해당 없음 |
| 외래키 참조 시 | 참조 무결성 검사 | 참조가 있으면 제한됨 | CASCADE/종속객체 확인 |
| 테이블 공간 | 보통 HWM 유지 | 보통 공간/HWM 초기화 | 해제됨 |
TRUNCATE가 DELETE보다 빠른 이유는 근본적으로 다른 방식으로 동작하기 때문입니다. DELETE는 행을 하나씩 찾아 삭제하면서 각 행의 Undo 로그를 기록합니다. 100만 행이면 100만 번의 행 단위 처리가 필요합니다. 반면 TRUNCATE는 보통 테이블이 사용하는 데이터 세그먼트나 저장 경계를 한 번에 초기화합니다.
PostgreSQL처럼 TRUNCATE 전용 트리거를 지원하는 DBMS도 있으므로, 실제 트리거 동작은 DELETE 트리거와 TRUNCATE 트리거를 구분해 확인해야 합니다.
Oracle 등 세그먼트 기반 저장 구조에서는 이 HWM(High Water Mark) 문제 때문에, 대량 DELETE 후에도 테이블의 풀 스캔 성능이 개선되지 않는 경우가 있습니다. 이런 경우에는 테이블을 REBUILD하거나 SHRINK해야 합니다.
운영 중 스키마 변경 전략
운영 데이터베이스의 스키마 변경은 가장 위험한 작업 중 하나입니다. 변경의 종류에 따라 위험도가 크게 다릅니다.
위험도별 스키마 변경 분류
🟢 낮은 위험 (대부분 안전):
* NULL 허용 컬럼 추가 (기존 행에 영향 없음)
* 상수 DEFAULT 컬럼 추가 (DBMS가 빠른 추가를 지원할 때)
* 컬럼 이름 변경 (애플리케이션 코드 수정 필요)
* 새로운 인덱스 생성 (Online DDL 사용 시)
🟡 중간 위험 (테스트 필수):
* 컬럼 크기 변경 (확대는 안전, 축소는 위험)
* NOT NULL 제약 추가 (기존 NULL 데이터 확인 필요)
* 외래키 추가 (기존 데이터 정합성 확인 필요)
* 인덱스 삭제 (의존하는 쿼리 확인 필요)
🔴 높은 위험 (점검 시간 고려):
* 대용량 테이블 컬럼 타입 변경 (전체 행 재작성)
* DEFAULT 없이 NOT NULL 컬럼 추가 (기존 행 위반)
* PRIMARY KEY 변경 (테이블 재구성)
* 테이블 파티셔닝 적용안전한 마이그레이션 절차
1. 사전 조사
□ 변경 대상 테이블의 데이터 건수 확인
□ 관련 인덱스, 뷰, 트리거, 프로시저 목록 확인
□ 외래키 참조 관계 확인
□ 해당 테이블을 사용하는 애플리케이션 쿼리 목록
2. 개발 환경 테스트
□ 동일 데이터 규모로 테스트 (10만 건 vs 1억 건은 다름)
□ 변경 소요 시간 측정
□ 락 발생 범위 확인
□ 롤백 시나리오 테스트
3. 변경 SQL 준비
□ 변경 SQL 작성 및 리뷰
□ 롤백 SQL 작성 (복원용)
□ 검증 SQL 작성 (변경 확인용)
4. 적용
□ 서비스 영향 최소화 (점검 시간 활용 / 온라인 DDL)
□ 변경 전 백업
□ 변경 적용
□ 검증 SQL 실행
□ 애플리케이션 정상 동작 확인
5. 사후 관리
□ 성능 모니터링 (새 인덱스, 변경된 쿼리 플랜)
□ 에러 로그 확인
□ 롤백 필요 시 준비된 SQL 실행DBMS별 Online DDL 지원
Online DDL은 락을 없애는 기능이 아니라, 작업에 필요한 잠금 범위와 시간을 줄이는 기능입니다. 작업 종류에 따라 짧은 메타데이터 락, 전체 테이블 스캔, 테이블 재작성은 여전히 발생할 수 있습니다.
-- MySQL 5.6+ Online DDL
ALTER TABLE employees ADD phone VARCHAR(20), ALGORITHM=INPLACE, LOCK=NONE;
-- ALGORITHM 옵션:
-- INPLACE: 가능한 경우 내부/온라인 방식으로 처리
-- 작업에 따라 재구성이 발생할 수 있음
-- COPY: 임시 테이블에 복사 후 교체 (느리지만 확실)
-- INSTANT: 지원 조건을 만족하면 메타데이터 중심으로 빠르게 처리
-- LOCK 옵션:
-- NONE: DML 동시 실행을 목표로 함
-- 짧은 메타데이터 락은 발생할 수 있음
-- SHARED: 읽기 허용, 쓰기 차단
-- EXCLUSIVE: 읽기/쓰기 모두 차단-- 컬럼 추가가 거의 즉시 완료 (메타데이터만 변경)
ALTER TABLE employees ADD phone VARCHAR(20), ALGORITHM=INSTANT;
-- 지원 조건을 만족하면 대용량 테이블에서도 매우 빠르게 완료-- 온라인 인덱스 생성
CREATE INDEX idx_emp_name ON employees(name) ONLINE;
-- 온라인 인덱스 재구성
ALTER INDEX idx_emp_name REBUILD ONLINE;
-- 온라인 테이블 재구성 (Oracle 12c+)
ALTER TABLE employees MOVE ONLINE;-- PostgreSQL ALTER TABLE은 작업 종류에 따라 AccessExclusiveLock,
-- 전체 테이블 스캔, 테이블 재작성 여부가 달라짐
-- NOT NULL 추가 등은 전체 테이블 스캔이 필요할 수 있음
-- NOT NULL 추가 시 CHECK 제약으로 대체 (큰 테이블에서 유용)
ALTER TABLE employees ADD CONSTRAINT chk_phone_nn CHECK (phone IS NOT NULL)
NOT VALID; -- 기존 데이터 검사 건너뛰기
-- 나중에 백그라운드에서 검증
ALTER TABLE employees VALIDATE CONSTRAINT chk_phone_nn;
-- 실제 NOT NULL 속성이 필요하면 검증 후 별도로 전환
ALTER TABLE employees ALTER COLUMN phone SET NOT NULL;이 CHECK 검증 패턴은 실제 NOT NULL 컬럼 속성과 완전히 같지 않습니다. 검증 후 SET NOT NULL을 적용할지, 적용 시 락과 스캔이 어떻게 발생하는지는 PostgreSQL 버전과 데이터 상태를 기준으로 확인해야 합니다.
무중단 스키마 변경 패턴 (Expand and Contract)
대규모 서비스에서는 Expand and Contract 패턴을 사용합니다. 이 패턴은 스키마 변경을 여러 단계로 나누어 각 단계에서 하위 호환성을 유지합니다.
이 패턴은 각 단계에서 롤백 경로를 남기고, 서비스 영향을 낮추면서 스키마를 변경할 수 있다는 장점이 있습니다.
pt-online-schema-change (MySQL)
MySQL에서 대용량 테이블의 스키마를 변경할 때 가장 많이 사용되는 외부 도구입니다. Percona Toolkit에 포함되어 있습니다.
1. 새로운 구조의 빈 테이블 _employees_new 생성
2. 기존 테이블에 트리거 설치 (INSERT/UPDATE/DELETE 동기화)
3. 기존 데이터를 배치로 복사 (청크 단위)
4. 복사 완료 후 테이블 이름 교체 (RENAME TABLE)
5. 기존 테이블과 트리거 삭제
장점:
* 복사 중에도 대부분 DML 가능 (짧은 잠금/부하 발생 가능)
* 최종 교체 락 시간이 짧은 편 (RENAME은 메타데이터 변경 중심)
단점:
* 트리거 오버헤드 (복사 중 DML 성능 저하)
* 디스크 공간이 2배 필요 (원본 + 복사본)
* FK가 있는 테이블에서는 사용이 복잡GitHub의 gh-ost도 비슷한 역할을 하는 도구입니다. gh-ost는 트리거 대신 바이너리 로그를 사용하여 동기화하므로 트리거의 단점을 피할 수 있습니다.
ALTER TABLE 실무 예제
쇼핑몰 테이블 발전 과정
서비스가 성장하면서 테이블이 어떻게 변경되는지 실제 시나리오를 따라가 봅시다.
-- 요구사항: 회원 등급 기능 추가
ALTER TABLE users ADD grade VARCHAR(10) DEFAULT 'BRONZE';
CREATE INDEX idx_users_grade ON users(grade);
-- 기존 VIP 고객 등급 업데이트 (매출 기준)
UPDATE users SET grade = 'GOLD'
WHERE user_id IN (
SELECT user_id FROM orders
GROUP BY user_id
HAVING SUM(total_amount) >= 1000000
);-- 요구사항: 사용자당 여러 연락처 저장
-- 기존 phone 컬럼은 1개만 저장 가능
-- 1. 연락처 테이블 생성
CREATE TABLE user_contacts (
contact_id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL REFERENCES users(user_id),
contact_type VARCHAR(10) NOT NULL, -- 'PHONE', 'FAX', 'MOBILE'
contact_value VARCHAR(20) NOT NULL,
is_primary BOOLEAN DEFAULT FALSE
);
-- 2. 기존 데이터 이전
INSERT INTO user_contacts (user_id, contact_type, contact_value, is_primary)
SELECT user_id, 'PHONE', phone, TRUE
FROM users
WHERE phone IS NOT NULL;
-- 3. 기존 컬럼 삭제 (데이터 이전 확인 후)
ALTER TABLE users DROP COLUMN phone;-- 요구사항: 회원 탈퇴 시 데이터 보존 (법적 의무)
ALTER TABLE users ADD is_deleted BOOLEAN DEFAULT FALSE;
ALTER TABLE users ADD deleted_at TIMESTAMP NULL;
-- 기존 DELETE 로직을 UPDATE로 변경해야 함
-- 기존: DELETE FROM users WHERE user_id = ?;
-- 변경: UPDATE users SET is_deleted = TRUE, deleted_at = NOW() WHERE user_id = ?;ALTER TABLE 성능에 영향을 주는 요소
| 요소 | 영향 | 대응 |
|---|---|---|
| 테이블 크기 | 행 수가 많을수록 느림 | pt-osc, gh-ost 사용 |
| 인덱스 수 | 인덱스가 많으면 재구성 시간 증가 | 불필요 인덱스 정리 후 변경 |
| 외래키 | FK 검사 오버헤드 | 참조 데이터 사전 정리, 비활성화는 최후수단 |
| LOB 컬럼 | BLOB/CLOB 이동은 매우 느림 | 별도 테이블로 분리 고려 |
| 리플리카 | 반영 지연(replication lag) 발생 | 리플리카 모니터링 필수 |
요약 정리
| 명령 | 역할 | 핵심 주의점 |
|---|---|---|
| ALTER TABLE ADD | 컬럼/제약 추가 | 기존 행 값 처리 필요 |
| ALTER TABLE MODIFY | 컬럼 타입/크기/제약 변경 | 기존 데이터 호환성 확인 |
| ALTER TABLE DROP | 컬럼/제약 삭제 | 종속 객체 확인 필수 |
| DROP TABLE | 테이블 완전 삭제 | CASCADE 시 FK만 삭제 |
| TRUNCATE TABLE | 데이터 전체 삭제 | 복구 가능성 DBMS별 확인 |
스키마 변경의 핵심은 변경 자체가 아니라 안전한 변경입니다. 개발 환경에서 충분히 테스트하고, 운영 환경에서는 Online DDL이나 외부 도구를 활용하여 서비스 영향을 최소화하며, 항상 복구/롤백 계획을 준비해 두어야 합니다. 특히 대용량 테이블에서의 스키마 변경은 반드시 DBA와 협의하여 진행하는 것이 좋습니다.
다음 장에서는 데이터를 실제로 조작하는 DML을 다루겠습니다.