데이터베이스 무결성 제약조건 설계와 관리
데이터베이스 무결성 제약조건의 유형과 SQL 구현 예시를 통해 데이터 품질, 일관성, 신뢰성을 설계하는 방법을 정리한다.
2026-08-15 · 최초 발행 2025-08-10
데이터가 들어오는 경계에서 일관성을 강제한다
무결성 제약조건은 데이터베이스에 저장되는 값의 오류를 막고 정확성·일관성·신뢰성을 유지하기 위한 규칙과 제한사항의 집합이다. 인가되지 않은 변경을 막고 허가된 행위만 데이터에 반영되도록 하며, 중복과 누락을 줄여 데이터베이스 품질을 지킨다. DBMS는 이러한 규칙을 바탕으로 데이터 무결성을 자동 검사하고 유지한다.
데이터의 무결성이 훼손되면 데이터 자체의 가치가 낮아지고, 이를 바탕으로 한 의사결정과 보고서도 신뢰하기 어려워진다. 무결한 데이터는 시스템 오류와 다운타임을 줄여 운영 효율에도 영향을 준다. 데이터 무결성이 법적·규제적 요구사항과 연결되는 산업에서는 더욱 직접적인 관리 대상이 된다.
테이블 식별자와 관계를 보호하는 규칙
개체 무결성은 행을 식별할 수 있게 한다
모든 테이블은 기본키(Primary Key)를 가져야 한다. 기본키에는 NULL이 들어갈 수 없고 중복된 값도 허용되지 않는다. 학생 테이블에서 학번을 기본키로 사용한다면, 각 학생은 고유한 학번을 가져야 하며 학번이 없는 학생 레코드는 존재할 수 없다.
CREATE TABLE Students (
student_id INTEGER PRIMARY KEY, -- 개체 무결성 적용
name VARCHAR(100) NOT NULL,
major VARCHAR(50)
);
참조 무결성은 연결된 테이블의 일관성을 유지한다
외래키(Foreign Key)는 참조 대상 테이블의 기본키에 있는 값만 가질 수 있다. 수강신청 테이블의 학번은 학생 테이블에 존재하는 학번만 사용할 수 있으므로, 관계를 맺은 테이블 사이의 불일치를 막는다.
CREATE TABLE Enrollments (
enrollment_id INTEGER PRIMARY KEY,
student_id INTEGER,
course_id INTEGER,
FOREIGN KEY (student_id) REFERENCES Students(student_id) -- 참조 무결성 적용
);
속성 규칙으로 값의 조건을 제한한다
속성 무결성은 특정 컬럼에 조건을 부여하는 방식이다. CHECK 제약조건은 값이 정해진 조건을 만족하는지 검사하고, NOT NULL은 NULL 값을 허용하지 않는다. DEFAULT는 값이 입력되지 않았을 때 기본값을 설정한다.
CREATE TABLE Products (
product_id INTEGER PRIMARY KEY,
product_name VARCHAR(100) NOT NULL, -- NOT NULL 제약조건
price DECIMAL(10, 2) CHECK (price > 0), -- CHECK 제약조건
stock INTEGER DEFAULT 0 -- DEFAULT 제약조건
);
업무 규칙은 사용자 정의 무결성으로 다룬다
사용자 정의 무결성은 업무 규칙이나 정책에 맞춰 직접 정의하는 제약조건이다. 단순한 값 범위, 필수값, 키 관계처럼 선언할 수 있는 규칙은 SQL 제약조건으로 구현한다. 여러 테이블과 조건이 얽힌 규칙, 이전 상태와의 비교, 감사 기록처럼 별도 처리가 필요한 경우에는 트리거를 적용한다.
트리거(Trigger)는 특정 이벤트가 발생할 때 실행되는 프로시저이며, 복잡한 업무 규칙, 감사 추적(Audit Trail), 자동 값 계산과 설정, 참조 무결성 유지에 사용할 수 있다. 아래 트리거는 재고가 0 미만으로 내려가지 않도록 한다.
-- 트리거 예시: 재고가 0 미만이 되지 않도록 보장
CREATE TRIGGER prevent_negative_stock
BEFORE UPDATE ON Products
FOR EACH ROW
BEGIN
IF NEW.stock < 0 THEN
SET NEW.stock = 0;
END IF;
END;
중복을 허용하지 않는 값은 UNIQUE로 관리한다
키 무결성은 UNIQUE 제약조건으로 특정 속성 또는 속성 집합의 값이 테이블 안에서 고유하도록 보장한다. 기본키와 달리 NULL 값을 허용할 수 있다.
CREATE TABLE Employees (
employee_id INTEGER PRIMARY KEY,
email VARCHAR(100) UNIQUE, -- 키 무결성 적용
phone VARCHAR(15) UNIQUE -- 키 무결성 적용
);
도메인 밖의 값이 저장되지 않게 한다
도메인 무결성은 속성 값이 정의된 값의 집합에 속하도록 보장한다. 데이터 타입, 형식, 범위를 제한하는 규칙이 여기에 해당하며, 데이터 삽입과 갱신 연산에서 적용된다. 다음 예시에서는 성별 컬럼에 'M' 또는 'F'만 저장할 수 있다.
-- 도메인 무결성 예시: 성별은 'M' 또는 'F'만 가능
CREATE TABLE Patients (
patient_id INTEGER PRIMARY KEY,
name VARCHAR(100),
gender CHAR(1) CHECK (gender IN ('M', 'F'))
);
CREATE TABLE 직원 (
사원번호 INT PRIMARY KEY,
이름 VARCHAR(50) NOT NULL,
나이 INT CHECK (나이 > 0 AND 나이 < 100),
이메일 VARCHAR(100) CHECK (이메일 LIKE '%@%.%')
);
릴레이션의 상태와 변화를 다루는 규칙
도메인 무결성이 개별 속성값의 허용 범위를 다룬다면, 릴레이션 무결성은 튜플을 삽입할 수 있는지와 릴레이션 사이의 관계가 적절히 유지되는지를 다룬다. 개체 무결성, 참조 무결성, 키 무결성은 릴레이션 수준에서 식별과 관계를 유지하는 규칙으로 연결된다.
릴레이션 규칙은 현재 상태를 검사하는지, 변화 자체를 막는지에 따라 나눌 수 있다. 상태제약(State Constraints)은 특정 시점의 데이터베이스 상태가 일관성을 가져야 한다는 정적 조건이고, 과도제약(Transition Constraints)은 이전 상태와 비교해 변화 방향을 제어하는 동적 규정이다. 또한 개별 행의 값만으로 판단하는 튜플제약(Tuple Constraints)과 튜플 집합 전체를 계산해야 판단할 수 있는 집합제약(Set Constraints)을 구분할 수 있다.
-- 상태제약 예시
ALTER TABLE 직원
ADD CONSTRAINT 최저임금체크 CHECK (급여 >= 9620);
-- 과도제약은 일반적으로 트리거를 통해 구현
CREATE TRIGGER 급여감소방지
BEFORE UPDATE ON 직원
FOR EACH ROW
BEGIN
IF NEW.급여 < OLD.급여 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '급여는 감소할 수 없습니다';
END IF;
END;
-- 튜플제약 예시
ALTER TABLE 직원
ADD CONSTRAINT 나이제한 CHECK (나이 >= 18);
-- 집합제약 예시 (뷰와 트리거를 통해 구현 가능)
CREATE VIEW 부서별평균급여 AS
SELECT 부서ID, AVG(급여) AS 평균급여
FROM 직원
GROUP BY 부서ID;
CREATE TRIGGER 부서급여제한
BEFORE INSERT OR UPDATE ON 직원
FOR EACH ROW
BEGIN
DECLARE 회사평균 DECIMAL(10,2);
DECLARE 부서평균 DECIMAL(10,2);
SELECT AVG(급여) INTO 회사평균 FROM 직원;
SELECT 평균급여 INTO 부서평균 FROM 부서별평균급여 WHERE 부서ID = NEW.부서ID;
IF 부서평균 > (회사평균 * 1.2) THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '부서 평균 급여가 제한을 초과합니다';
END IF;
END;
검사 시점도 규칙 설계의 일부다. 즉시제약(Immediate Constraints)은 삽입·삭제·갱신 작업이 실행되는 즉시 검사하며, 기본키 중복 검사가 대표적이다. 지연제약(Deferred Constraints)은 트랜잭션이 끝나는 시점까지 검사를 미루므로 복잡한 참조 무결성이나 여러 테이블을 갱신하는 작업, 부모-자식 테이블의 상호 참조 관계에서 활용할 수 있다.
-- PostgreSQL에서 지연 제약 예시
BEGIN;
SET CONSTRAINTS ALL DEFERRED;
-- 여러 연관 테이블 업데이트 작업 수행
UPDATE 부서 SET 부서장ID = 102 WHERE 부서ID = 10;
UPDATE 직원 SET 부서ID = 20 WHERE 사원번호 = 102;
COMMIT; -- 이 시점에서 모든 제약조건 검사
-- 직원 테이블에 새 레코드가 추가될 때마다 감사 로그를 기록하는 트리거
CREATE TRIGGER 직원추가감사
AFTER INSERT ON 직원
FOR EACH ROW
BEGIN
INSERT INTO 감사로그(테이블명, 작업유형, 사용자, 작업시간)
VALUES ('직원', 'INSERT', CURRENT_USER(), NOW());
END;
CREATE TABLE 상품 (
상품ID INT,
상품명 VARCHAR(100) NOT NULL,
가격 DECIMAL(10,2) CHECK (가격 > 0),
재고 INT DEFAULT 0 CHECK (재고 >= 0)
);
CREATE TABLE 주문 (
주문ID INT PRIMARY KEY,
고객ID INT,
주문일자 DATE NOT NULL,
총액 DECIMAL(10,2),
FOREIGN KEY (고객ID) REFERENCES 고객(고객ID),
CONSTRAINT 총액검증 CHECK (총액 > 0)
);
-- Oracle에서의 어셜션 구현 예시 (뷰와 트리거 조합)
CREATE VIEW 재고부족상품 AS
SELECT 상품ID, 상품명, 재고
FROM 상품
WHERE 재고 < 10;
CREATE TRIGGER 재고확인
BEFORE INSERT OR UPDATE ON 주문상세
FOR EACH ROW
DECLARE
v_재고 NUMBER;
BEGIN
SELECT 재고 INTO v_재고
FROM 상품
WHERE 상품ID = :NEW.상품ID;
IF v_재고 < :NEW.수량 THEN
RAISE_APPLICATION_ERROR(-20001, '재고가 부족합니다');
END IF;
END;
금융 데이터 모델에서 제약조건을 배치하는 방식
계좌, 고객, 거래가 연결된 금융 시스템에서는 잔액과 거래 유형, 필수 정보, 식별자에 각각 다른 제약을 적용할 수 있다.
계좌 잔액에는 0 미만을 허용하지 않는 CHECK 제약조건을 적용한다. 거래 유형은 'Deposit', 'Withdrawal', 'Transfer'로 제한하며, 고객 이름·거래 금액·거래 날짜처럼 빠지면 안 되는 정보는 NOT NULL로 보장한다. 이메일 주소는 UNIQUE 제약조건으로 중복을 막는다.
의료 정보의 필수값과 허용값을 통제하는 방식
의료 정보 시스템은 환자, 의사, 예약 데이터의 관계와 입력값을 함께 관리해야 한다.
환자 이름과 생년월일은 NULL이 될 수 없으며, 성별은 'M' 또는 'F'만 가능하다. 혈액형 역시 정해진 타입만 허용한다. 의사 면허번호에는 UNIQUE 제약조건을 적용해 중복을 막고, 모든 예약에는 시간이 지정되도록 NOT NULL을 사용한다.
제약이 깨졌을 때 시스템에 남는 문제
참조 무결성이 깨지면 연관된 테이블 사이에 데이터 불일치가 생긴다. 잘못된 값은 오류가 포함된 보고서로 이어질 수 있고, 무결성 규칙을 전제로 작성한 애플리케이션 로직은 예상하지 못한 동작을 보일 수 있다. 위반이 심각한 경우에는 전체 시스템의 오작동으로 번질 가능성도 있다.
설계부터 변경 관리까지 이어지는 운영 원칙
제약조건은 데이터베이스 설계 단계에서 식별하고 구현해야 한다. DBMS 수준의 제약조건과 애플리케이션 수준의 유효성 검사를 함께 적용하는 다중 방어 전략도 필요하다.
운영 중에는 스크립트나 도구로 데이터 무결성을 정기 점검하고, 각 제약조건을 개발자와 관리자가 이해할 수 있도록 문서화한다. 제약조건을 바꿀 때는 영향 분석을 수행한 뒤 신중하게 적용해야 한다. 이러한 관리가 데이터 오류를 줄이고 시스템의 안정성과 신뢰성을 뒷받침한다.