icon

안동민 개발노트

3장 : SQL 기초 — DDL

제약 조건


데이터베이스에 잘못된 데이터가 들어가면 시스템 전체가 혼란에 빠집니다. 나이가 -5인 회원, 존재하지 않는 부서에 배정된 직원, 같은 이메일로 가입한 두 명의 회원. 이런 문제를 애플리케이션 코드에서만 검증하면 실수나 버그로 잘못된 데이터가 들어갈 수 있습니다. 제약 조건(Constraint)은 DBMS가 데이터의 무결성을 자동으로 보장하는 규칙입니다. 어떤 경로로 데이터가 들어오든(SQL 직접 실행, 애플리케이션, 배치 프로그램) 제약 조건을 만족하지 않으면 DBMS가 거부합니다.

제약 조건은 데이터 품질의 마지막 방어선입니다. 애플리케이션에서 검증하더라도 DBMS에 제약 조건을 설정해야 하고, 반대로 DBMS에 제약 조건이 있더라도 불필요한 에러를 줄이기 위해 애플리케이션에서도 검증해야 합니다. 이 이중 검증이 실무의 표준입니다.

이 절에서는 각 제약 조건의 개념과 문법, DBMS별 차이, 복합 키, 외래키의 참조 옵션, 제약 조건 관리, 그리고 실무에서의 설계 원칙까지 깊이 있게 다룹니다.


제약 조건의 종류

제약 조건역할허용예시
PRIMARY KEY행을 유일하게 식별NULL 불가, 중복 불가사원번호, 주문번호
FOREIGN KEY다른 테이블과의 관계 보장참조 테이블에 있는 값만 허용주문의 고객ID
UNIQUE중복 불가NULL 허용 (DBMS마다 다름)이메일, 주민번호
NOT NULLNULL 불가빈 값 금지이름, 비밀번호
CHECK조건식 검사조건을 만족하는 값만 허용나이 > 0, 성별 IN ('M','F')
DEFAULT값 미지정 시 기본값제약보다는 편의 기능가입일 = 오늘, 상태 = 'ACTIVE'

PRIMARY KEY — 기본키

기본키는 테이블에서 각 행을 유일하게 식별하는 컬럼(또는 컬럼 조합)입니다. 기본키의 핵심 특성은 두 가지입니다. 값이 유일해야 하고(UNIQUE), NULL이어서는 안 됩니다(NOT NULL). 즉 PRIMARY KEY = UNIQUE + NOT NULL입니다.

기본키 정의
-- 컬럼 레벨 정의
CREATE TABLE employees (
    emp_id INT PRIMARY KEY,
    name   VARCHAR(100) NOT NULL
);

-- 테이블 레벨 정의 (이름 지정)
CREATE TABLE employees (
    emp_id INT,
    name   VARCHAR(100) NOT NULL,
    CONSTRAINT pk_emp PRIMARY KEY (emp_id)
);

복합 기본키

두 개 이상의 컬럼을 조합하여 기본키로 사용할 수 있습니다. 다대다(M:N) 관계의 연결 테이블에서 흔히 사용됩니다.

복합 기본키
-- 학생-과목 수강 테이블 (학생ID + 과목ID로 식별)
CREATE TABLE enrollment (
    student_id INT,
    course_id  INT,
    grade      VARCHAR(2),
    CONSTRAINT pk_enrollment PRIMARY KEY (student_id, course_id)
);

-- (1, 101, 'A')와 (1, 102, 'B')는 허용
-- (1, 101, 'A')와 (1, 101, 'B')는 중복 — 같은 학생이 같은 과목을 두 번 수강

복합키에서 유의할 점은, 개별 컬럼은 중복될 수 있지만 조합이 유일해야 한다는 것입니다. student_id=1이 여러 번 나올 수 있고, course_id=101이 여러 번 나올 수 있지만, (student_id=1, course_id=101) 조합은 한 번만 나올 수 있습니다.

자연키 vs 대리키

기본키를 선택할 때 두 가지 접근이 있습니다.

자연키 vs 대리키
자연키 (Natural Key):
  비즈니스 의미가 있는 컬럼을 기본키로 사용
  예: 주민등록번호, 이메일, ISBN

  장점: 별도의 ID 컬럼 불필요
  단점: 비즈니스 규칙 변경 시 키도 변경해야 함
        (예: 주민번호 수집 금지법)

대리키 (Surrogate Key):
  비즈니스 의미 없이 순수하게 식별만을 위한 컬럼
  예: AUTO_INCREMENT, SEQUENCE, UUID

  장점: 비즈니스 변화에 영향 없음, 조인 성능 우수
  단점: 별도의 ID 컬럼 필요, 비즈니스 의미 없음

실무에서는 대리키를 기본키로 사용하고, 자연키는 UNIQUE 제약으로 관리하는 것이 일반적입니다. 비즈니스 규칙은 변할 수 있지만, 대리키는 변하지 않기 때문입니다.

기본키와 인덱스

기본키를 정의하면 DBMS가 자동으로 인덱스를 생성합니다. Oracle은 기본키에 유니크 인덱스를, MySQL InnoDB는 클러스터드 인덱스를 생성합니다. 이 인덱스 덕분에 기본키로 조회하는 쿼리가 매우 빠릅니다.

DBMS별 기본키 인덱스
Oracle:     UNIQUE INDEX (비클러스터드)
MySQL:      CLUSTERED INDEX (행 데이터가 PK 순서로 저장됨)
PostgreSQL: UNIQUE INDEX (비클러스터드)
SQL Server: CLUSTERED INDEX (기본, 변경 가능)

MySQL InnoDB에서 클러스터드 인덱스는 테이블 데이터의 물리적 저장 순서를 결정합니다. 따라서 기본키가 정수형 AUTO_INCREMENT이면 새 행이 항상 마지막에 추가되어 효율적이지만, UUID처럼 무작위 값이면 데이터 페이지가 조각나서 성능이 저하될 수 있습니다.


FOREIGN KEY — 외래키

외래키는 두 테이블 간의 관계를 정의하고, 참조 무결성을 보장합니다. 자식 테이블의 외래키 값은 부모 테이블의 참조된 컬럼에 반드시 존재해야 합니다(또는 NULL).

외래키 정의
-- 컬럼 레벨
CREATE TABLE orders (
    order_id INT PRIMARY KEY,
    user_id  INT REFERENCES users(user_id),
    amount   INT NOT NULL
);

-- 테이블 레벨 (이름 지정)
CREATE TABLE orders (
    order_id INT PRIMARY KEY,
    user_id  INT NOT NULL,
    amount   INT NOT NULL,
    CONSTRAINT fk_orders_user FOREIGN KEY (user_id)
        REFERENCES users(user_id)
);

외래키가 참조하는 부모 테이블의 컬럼은 반드시 PRIMARY KEY이거나 UNIQUE 제약이 있어야 합니다. 유일하지 않은 컬럼을 참조하면 어떤 행을 가리키는가가 모호해지기 때문입니다.

복합 외래키

부모 테이블의 기본키가 복합키이면 외래키도 같은 컬럼 조합을 참조해야 합니다.

복합 외래키
CREATE TABLE exam_scores (
    score_id   INT PRIMARY KEY,
    student_id INT NOT NULL,
    course_id  INT NOT NULL,
    score      INT,
    CONSTRAINT fk_scores_enrollment
        FOREIGN KEY (student_id, course_id)
        REFERENCES enrollment(student_id, course_id)
);

자기 참조 외래키 (Self-Referencing FK)

같은 테이블 내에서 외래키를 참조하는 패턴입니다. 조직도(상사-부하 관계), 카테고리 계층 구조 등에서 사용됩니다.

자기 참조 외래키
CREATE TABLE employees (
    emp_id     INT PRIMARY KEY,
    name       VARCHAR(100) NOT NULL,
    manager_id INT,
    CONSTRAINT fk_emp_manager FOREIGN KEY (manager_id)
        REFERENCES employees(emp_id)
);

-- 최상위 관리자 (사장)
INSERT INTO employees VALUES (1, '사장', NULL);
-- 부장 (사장의 부하)
INSERT INTO employees VALUES (2, '부장', 1);
-- 과장 (부장의 부하)
INSERT INTO employees VALUES (3, '과장', 2);

ON DELETE / ON UPDATE 옵션

부모 테이블의 행이 삭제되거나 기본키가 변경되었을 때, 자식 테이블의 외래키를 어떻게 처리할지 결정합니다.

참조 동작 옵션
departments 테이블
┌────┬───────┐
│ id │ name  │
├────┼───────┤
│ 1  │ 개발팀│
│ 2  │ 기획팀│
└────┴───────┘

employees 테이블
┌────┬──────┬─────────┐
│ id │ name │ dept_id │
├────┼──────┼─────────┤
│ 1  │김철수│    1    │
│ 2  │이영희│    1    │
│ 3  │박민수│    2    │
└────┴──────┴─────────┘

employees.dept_id → departments.id

DELETE FROM departments WHERE id = 1;  → 어떻게 되나?
옵션부모 삭제 시 동작사용 시나리오
RESTRICT (기본)에러! 삭제 거부안전이 최우선인 경우
NO ACTIONRESTRICT와 유사 (체크 시점이 다름)SQL 표준 기본값
CASCADE자식 행도 함께 삭제주문 삭제 → 주문상세도 삭제
SET NULL자식의 FK를 NULL로 변경부서 삭제 → 직원의 부서를 NULL로
SET DEFAULT자식의 FK를 DEFAULT 값으로 변경지원 DBMS 제한적
참조 옵션 적용
CREATE TABLE employees (
    emp_id   INT PRIMARY KEY,
    name     VARCHAR(100) NOT NULL,
    dept_id  INT,
    CONSTRAINT fk_emp_dept FOREIGN KEY (dept_id)
        REFERENCES departments(dept_id)
        ON DELETE SET NULL
        ON UPDATE CASCADE
);

ON DELETE SET NULL은 부서가 삭제되면 해당 부서 직원의 dept_id를 NULL로 변경합니다. ON UPDATE CASCADE는 부서 ID가 변경되면 직원의 dept_id도 자동으로 같은 값으로 변경됩니다.

CASCADE의 위험성
users → orders → order_items → shipments

users에 ON DELETE CASCADE가 설정되어 있다면:
DELETE FROM users WHERE user_id = 1;

→ user_id=1의 주문 삭제
  → 해당 주문의 주문상세 삭제
    → 해당 주문상세의 배송정보 삭제

한 명의 회원 삭제로 수백 건의 데이터가 연쇄 삭제!
→ 실무에서는 CASCADE를 신중하게 사용해야 합니다

UNIQUE — 유일성 제약

UNIQUE 제약은 컬럼의 값이 테이블 내에서 유일해야 함을 보장합니다. PRIMARY KEY와의 차이는 NULL을 허용한다는 점과, 한 테이블에 여러 개의 UNIQUE 제약을 둘 수 있다는 점입니다.

UNIQUE 제약
CREATE TABLE users (
    user_id   INT PRIMARY KEY,
    email     VARCHAR(100) NOT NULL,
    phone     VARCHAR(20),
    CONSTRAINT uq_email UNIQUE (email),
    CONSTRAINT uq_phone UNIQUE (phone)
);

NULL과 UNIQUE의 관계

UNIQUE 컬럼에 NULL이 여러 개 들어갈 수 있는지는 DBMS마다 다릅니다.

DBMSUNIQUE 컬럼에 NULL 여러 개 허용?
Oracle허용
MySQL허용
PostgreSQL허용
SQL Server불허 (NULL도 하나만 가능)

SQL Server는 NULL도 하나의 값으로 취급하여 UNIQUE 제약을 적용합니다. 이 차이를 모르면 DBMS를 바꿀 때 예상치 못한 에러가 발생할 수 있습니다.

복합 UNIQUE

여러 컬럼의 조합에 유일성을 적용할 수 있습니다. 개별 컬럼은 중복 가능하지만, 조합이 유일해야 합니다.

복합 UNIQUE
-- 같은 부서에 같은 직급은 하나만 (부서-직급 조합 유일)
CREATE TABLE positions (
    dept_id  INT,
    title    VARCHAR(50),
    emp_id   INT,
    CONSTRAINT uq_dept_title UNIQUE (dept_id, title)
);

NOT NULL

NOT NULL은 해당 컬럼에 NULL 값을 허용하지 않는 제약입니다. 반드시 값이 있어야 하는 컬럼에 설정합니다.

NOT NULL
CREATE TABLE users (
    user_id  INT PRIMARY KEY,
    username VARCHAR(50) NOT NULL,      -- 반드시 입력
    email    VARCHAR(100) NOT NULL,     -- 반드시 입력
    phone    VARCHAR(20)                -- NULL 허용
);

NOT NULL은 다른 제약 조건과 달리 컬럼 레벨에서만 정의할 수 있습니다. 테이블 레벨에서 CONSTRAINT ... NOT NULL 형태로 정의할 수 없습니다(Oracle에서는 가능하지만 비표준).

NULL을 허용할지 결정하는 기준

NOT NULL 결정 기준
반드시 NOT NULL:
  * 기본키 (PK에 자동 포함)
  * 로그인에 필수인 컬럼 (username, email, password)
  * 비즈니스 로직에 반드시 필요한 값 (주문의 상품ID, 수량)
  * 상태값 (status, is_active 등)

NULL 허용 가능:
  * 선택적 입력 정보 (phone, address, bio)
  * 나중에 결정되는 값 (배송일, 결제일)
  * 외래키 (관계가 선택적인 경우)
  * 자기 참조 FK (최상위 노드는 부모가 없음)

CHECK — 조건 검사

CHECK 제약은 컬럼에 입력되는 값이 특정 조건을 만족하는지 검사합니다.

CHECK 제약
CREATE TABLE employees (
    emp_id   INT PRIMARY KEY,
    name     VARCHAR(100) NOT NULL,
    age      INT CHECK (age >= 0 AND age <= 150),
    salary   INT,
    gender   CHAR(1),
    CONSTRAINT ck_salary CHECK (salary >= 0),
    CONSTRAINT ck_gender CHECK (gender IN ('M', 'F'))
);

CHECK 제약의 한계

CHECK 제약은 단일 행의 단일 테이블에 대해서만 검사할 수 있습니다. 다른 테이블을 참조하는 조건이나 서브쿼리는 사용할 수 없습니다.

CHECK의 한계
-- 가능
CHECK (price >= 0)
CHECK (status IN ('ACTIVE', 'INACTIVE', 'DELETED'))
CHECK (start_date < end_date)          -- 같은 행의 다른 컬럼 참조 가능

-- 불가능
CHECK (salary < (SELECT max_salary FROM pay_grades WHERE ...))  -- 서브쿼리
CHECK (email NOT IN (SELECT email FROM blocked_emails))          -- 다른 테이블

다른 테이블을 참조하는 복잡한 검증은 트리거나 애플리케이션 로직으로 구현해야 합니다.

MySQL의 CHECK 제약 주의사항

MySQL 8.0.16 미만 버전에서는 CHECK 제약을 구문적으로 허용하지만 실제로 검사하지 않습니다. 즉, CHECK 제약을 정의해도 제약 위반 데이터가 들어갑니다. MySQL 8.0.16부터 정상적으로 작동합니다.

MySQL 버전 확인
SELECT VERSION();
-- 8.0.16 이상이어야 CHECK가 실제로 동작

DEFAULT — 기본값

DEFAULT는 INSERT 시 값을 명시하지 않으면 자동으로 설정되는 값입니다. 엄밀히 말하면 제약 조건이 아니라 편의 기능이지만, 제약 조건과 함께 다루는 것이 일반적입니다.

DEFAULT
CREATE TABLE orders (
    order_id    INT PRIMARY KEY,
    status      VARCHAR(20) DEFAULT 'PENDING',
    created_at  TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    is_deleted  BOOLEAN DEFAULT FALSE
);

-- status, created_at, is_deleted를 생략하면 DEFAULT 값 적용
INSERT INTO orders (order_id) VALUES (1);
-- 결과: order_id=1, status='PENDING', created_at=현재시각, is_deleted=FALSE

DEFAULT에는 상수뿐 아니라 함수도 사용할 수 있습니다. CURRENT_TIMESTAMP, CURRENT_DATE, SYSDATE(Oracle) 등이 흔히 사용됩니다.


제약 조건 이름 지정

모든 제약 조건에는 이름을 지정하는 것이 좋습니다. 이름이 없으면 DBMS가 자동으로 이름을 생성하는데(SYS_C007234 같은 형태), 에러 발생 시 어떤 제약을 위반했는지 알기 어렵습니다.

이름 지정 규칙
CREATE TABLE employees (
    id         NUMBER,
    name       VARCHAR2(100),
    email      VARCHAR2(200),
    dept_id    NUMBER,
    salary     NUMBER,
    CONSTRAINT pk_emp         PRIMARY KEY (id),
    CONSTRAINT uq_emp_email   UNIQUE (email),
    CONSTRAINT fk_emp_dept    FOREIGN KEY (dept_id)
        REFERENCES departments(dept_id)
        ON DELETE SET NULL,
    CONSTRAINT ck_emp_salary  CHECK (salary >= 0),
    CONSTRAINT nn_emp_name    CHECK (name IS NOT NULL)
);
이름 지정의 효과
이름 없을 때 에러
  ORA-02290: check constraint (HR.SYS_C007234) violated
  → "SYS_C007234가 뭐지?" → 데이터 딕셔너리 조회 필요

이름 있을 때 에러
  ORA-02290: check constraint (HR.CK_EMP_SALARY) violated
  → "아, 급여가 음수구나!" → 즉시 원인 파악

제약 조건 이름 규칙

접두어의미예시
pk_PRIMARY KEYpk_employees
fk_FOREIGN KEYfk_orders_user
uq_UNIQUEuq_users_email
ck_CHECKck_age_positive
nn_NOT NULLnn_users_name

SEQUENCE와 자동 증가

기본키에 자동으로 증가하는 숫자를 할당하는 방법은 DBMS마다 다릅니다.

Oracle — SEQUENCE

Oracle SEQUENCE
-- 시퀀스 생성
CREATE SEQUENCE emp_seq
    START WITH 1
    INCREMENT BY 1
    MAXVALUE 9999999
    NOCACHE;        -- 캐시 없이 순차 생성

-- 사용
INSERT INTO employees (id, name)
VALUES (emp_seq.NEXTVAL, '김철수');

-- 현재 값 확인
SELECT emp_seq.CURRVAL FROM DUAL;

-- 시퀀스 수정
ALTER SEQUENCE emp_seq INCREMENT BY 10;

-- 시퀀스 삭제
DROP SEQUENCE emp_seq;

SEQUENCE는 테이블과 독립적인 객체입니다. 하나의 시퀀스를 여러 테이블에서 공유할 수 있고, 하나의 테이블에 여러 시퀀스를 사용할 수도 있습니다.

CACHE 옵션은 성능에 영향을 줍니다. CACHE 20이면 메모리에 20개의 시퀀스 값을 미리 생성해두어 디스크 접근을 줄입니다. 하지만 DBMS 비정상 종료 시 캐시된 값이 손실되어 번호가 건너뛸 수 있습니다.

MySQL — AUTO_INCREMENT

MySQL AUTO_INCREMENT
CREATE TABLE employees (
    id   INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL
);

-- id를 지정하지 않으면 자동 증가
INSERT INTO employees (name) VALUES ('김철수');
INSERT INTO employees (name) VALUES ('이영희');

-- 마지막 생성된 ID 확인
SELECT LAST_INSERT_ID();

-- AUTO_INCREMENT 값 재설정
ALTER TABLE employees AUTO_INCREMENT = 100;

AUTO_INCREMENT의 특징은 테이블당 하나만 사용할 수 있고, 반드시 인덱스(PK 또는 UNIQUE)가 있어야 한다는 것입니다.

PostgreSQL — SERIAL / IDENTITY

PostgreSQL 자동 증가
-- SERIAL (레거시)
CREATE TABLE employees (
    id   SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL
);
-- 내부적으로 시퀀스 자동 생성

-- IDENTITY (SQL 표준, PostgreSQL 10+)
CREATE TABLE employees (
    id   INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    name VARCHAR(100) NOT NULL
);

-- GENERATED BY DEFAULT: 명시적 값 지정 허용
CREATE TABLE employees (
    id   INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    name VARCHAR(100) NOT NULL
);

GENERATED ALWAYS는 ID를 직접 지정할 수 없고, GENERATED BY DEFAULT는 직접 지정도 허용합니다. 데이터 마이그레이션 시에는 BY DEFAULT가 편리합니다.

Oracle 12c+ — IDENTITY

Oracle 12c+ IDENTITY
CREATE TABLE employees (
    id   NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    name VARCHAR2(100) NOT NULL
);

Oracle 12c부터는 SEQUENCE를 직접 만들지 않아도 MySQL과 유사한 자동 증가 컬럼을 사용할 수 있습니다.

DBMS방식테이블당 제한여러 테이블 공유
OracleSEQUENCE제한 없음가능
Oracle 12c+IDENTITY1개불가
MySQLAUTO_INCREMENT1개불가
PostgreSQLSERIAL/IDENTITY제한 없음불가 (SERIAL은 내부 시퀀스)
SQL ServerIDENTITY1개불가

제약 조건 관리

제약 조건 조회

Oracle — 제약 조건 조회
-- 테이블의 제약 조건 목록
SELECT constraint_name, constraint_type, status
FROM user_constraints
WHERE table_name = 'EMPLOYEES';

-- 제약 조건의 컬럼 정보
SELECT constraint_name, column_name
FROM user_cons_columns
WHERE table_name = 'EMPLOYEES';
MySQL — 제약 조건 조회
-- 테이블 구조와 제약 조건 확인
SHOW CREATE TABLE employees;

-- information_schema에서 조회
SELECT constraint_name, constraint_type
FROM information_schema.table_constraints
WHERE table_name = 'employees';
PostgreSQL — 제약 조건 조회
-- 테이블의 제약 조건
SELECT conname, contype, pg_get_constraintdef(oid)
FROM pg_constraint
WHERE conrelid = 'employees'::regclass;

제약 조건 추가와 삭제

이미 데이터가 있는 테이블에 제약 조건을 추가할 때는 기존 데이터가 제약을 만족하는지 확인해야 합니다.

제약 조건 추가 전 확인
-- NOT NULL 추가 전: NULL 데이터 확인
SELECT COUNT(*) FROM employees WHERE phone IS NULL;

-- CHECK 추가 전: 조건 위반 데이터 확인
SELECT * FROM employees WHERE salary < 0;

-- UNIQUE 추가 전: 중복 데이터 확인
SELECT email, COUNT(*) FROM employees GROUP BY email HAVING COUNT(*) > 1;

-- FK 추가 전: 참조 무결성 위반 데이터 확인
SELECT e.dept_id FROM employees e
WHERE e.dept_id NOT IN (SELECT dept_id FROM departments);

이런 확인 없이 제약 조건을 추가하면 기존 데이터가 제약을 위반합니다라는 에러가 발생합니다.


제약 조건 설계 원칙

실무 설계 원칙
1. 기본키는 대리키(INT AUTO_INCREMENT)를 사용하라
   → 비즈니스 키(이메일, 주민번호)는 UNIQUE로 관리

2. 외래키는 반드시 설정하라
   → 애플리케이션만으로는 참조 무결성을 보장할 수 없음
   → 성능 이유로 FK를 생략하는 팀도 있지만, 데이터 정합성 위험

3. NOT NULL을 적극 사용하라
   → "이 컬럼이 NULL일 수 있는가?"를 항상 고민
   → NULL을 허용하면 모든 쿼리에서 NULL 처리가 필요해짐

4. CHECK로 비즈니스 규칙을 DBMS 레벨에서 보장하라
   → price >= 0, quantity > 0, status IN (...) 등

5. 제약 조건에는 반드시 이름을 붙여라
   → 에러 추적과 유지보수가 쉬워짐

6. CASCADE는 신중하게 사용하라
   → 연쇄 삭제의 범위를 정확히 이해하고 적용
   → 대부분의 경우 RESTRICT + 소프트 삭제가 안전

요약

제약역할NULL중복테이블당 개수
PRIMARY KEY행 식별불가불가1개
FOREIGN KEY관계 보장가능가능여러 개
UNIQUE중복 방지가능 (DBMS마다)불가여러 개
NOT NULL빈값 방지불가가능여러 개
CHECK조건 검사여러 개
DEFAULT기본값여러 개

제약 조건은 데이터 품질의 마지막 방어선입니다. 애플리케이션의 버그, 잘못된 SQL, 다른 팀의 직접 데이터 수정 등 어떤 경로를 통하더라도 DBMS가 제약 조건을 검사하여 잘못된 데이터의 입력을 거부합니다. 성능 최적화를 위해 제약 조건을 제거하는 것은 단기적으로는 빠르지만, 장기적으로는 데이터 품질 저하와 디버깅 비용 증가로 이어집니다.

다음 절에서는 테이블을 수정하고 삭제하는 방법을 다루겠습니다.

목차