[PostgreSQL] 제약조건 설정시 주의해야할 부분을 고려하면서(PRIMARY KEY, NOT NULL, UNIQUE, CHECK) - 컴도리돌이
이전 포스팅에서 제약조건을 간단하게 설정하는 방법과 제약조건을 설정할 때 어느 부분을 고려해야 할지 짧게 확인해 보았어요. 이번에는 조금 더 세부적으로 설정하는 방법과 언제 사용하는지에 대해 스터디를 해보려고 해요.
적절하게 제약조건을 걸자!
제약조건을 설정할 때 적재적소하게 사용하는 게 중요하다고 했죠? 모든 필드에 제약조건을 걸고 싶은 유혹이 있을 수 있지만, 정말 필요한 곳에만 정확이 적용하는 것이 중요해요. 예를 들어, 사용자 테이블에서 필수 정보와 선택 정보를 구분해 본다 다음과 같아요.
CREATE TABLE users (
user_id SERIAL PRIMARY KEY, -- 각 사용자를 고유하게 식별하는 값
username VARCHAR(50) NOT NULL, -- 사용자 이름은 필수
email VARCHAR(100) UNIQUE NOT NULL, -- 이메일은 필수이면서 중복이 안 됨
phone_number VARCHAR(15), -- 전화번호는 선택 (NULL 허용)
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -- 계정 생성 시각, 기본값 설정
);
이 코드에서 볼 수 있듯이, 필수 정보 (username, email)에만 NOT NULL 제약조건을 설정했어요. 선택 정보인 phone_number는 NULL을 허용해 유연성을 높였죠. 또 이메일 필드에는 UNIQUE 제약 조건을 사용해 중복된 이메일이 입력되지 않도록 했습니다.
이처럼, 필요한 제약조건만 적절히 사용하면 데이터 베이스의 무결성을 유지하면서도 유연성을 확보할 수 있죠.
확장성을 고려해서 제약조건을 걸자!
지금 설계한 테이블 구조가 어떻게 변화할지 모르는 일이에요. 그래서 미래의 변화에 대비해 제약 조건을 설정하는 것도 중요해요. 🔥
현재는 한 사용자에게 하나의 이메일만 할당되도록 설정했지만, 나중에 여러 이메일을 허용할 가능성을 고려할 수 있어요. 이를 위해, 이메일 테이블을 별도로 분리하고 제약조건을 설정해 보는 방법이 있습니다.
CREATE TABLE user_emails (
email_id SERIAL PRIMARY KEY,
user_id INT NOT NULL,
email VARCHAR(100) NOT NULL,
is_primary BOOLEAN DEFAULT FALSE,
CONSTRAINT fk_user
FOREIGN KEY(user_id)
REFERENCES users(user_id)
ON DELETE CASCADE,
CONSTRAINT unique_user_email UNIQUE(user_id, email)
);
user_emails라는 테이블을 만들어 사용자의 여러 이메일을 관리할 수 있도록 했어요. UNIQUE(user_id, email) 제약 조건을 통해, 한 사용자가 동일한 이메일을 여러 번 등록하지 못하도록 방지했죠. 이렇게 하면, 나중에 요구사항이 변경되더라도 구조를 쉽게 확장할 수 있어요.
협업을 고려해서 제약조건을 걸자!
지금 설정한 제약 조건을 다른 개발자들은 모를 수가 있어요 🙄 그럴 때를 대비해서 제약 조건의 의도를 쉽게 이해할 수 있도록, 명확한 코드와 함께 주석을 달아두는 것이 좋아요.
CREATE TABLE orders (
order_id SERIAL PRIMARY KEY,
user_id INT NOT NULL,
product_id INT NOT NULL,
quantity INT CHECK (quantity > 0), -- 수량은 0보다 커야 한다는 제약조건
order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_user
FOREIGN KEY(user_id)
REFERENCES users(user_id)
ON DELETE CASCADE, -- 사용자가 삭제되면 해당 주문도 삭제
CONSTRAINT fk_product
FOREIGN KEY(product_id)
REFERENCES products(product_id)
);
각 제약 조건이 어떤 역할을 하는지 주석으로 설명이 되어 있죠. 예를 들어, quantity 필드에는 CHECK (quantity > 0) 제약 조건을 설정해 잘못된 수량이 입력되지 않도록 했고, FOREIGN KEY 제약 조건을 통해 사용자가 삭제되면 해당 주문도 자동으로 삭제되도록 했어요. 주석을 통해, 이 제약조건이 왜 필요한지 명확히 설명하면, 다른 개발자들이 코드를 이해하는 데 큰 도움이 될 겁니다 ✅
성능을 고려해서 제약조건을 걸자!
제약 조건을 설정할 때 데이터베이스의 성능에 미치는 영향을 고려하는 것이 매우 중요하죠. 제약 조건은 데이터 무결성을 유지하는 데 매우 유용하지만, 특히 대용량 데이터를 다루는 경우 성능에 영향을 줄 수 있어요. 🙅♂️
예를 들어 CHECK 제약조건이나 복합 UNIQUE 제약 조건을 설정하기 전에 성능 테스트를 진행하거나, 필요에 따라 인덱스를 추가하여 성능을 최적화할 수 있는 방법을 고민해봐야 해요 🤔
CREATE TABLE transactions (
transaction_id SERIAL PRIMARY KEY,
account_id INT NOT NULL,
amount DECIMAL(10, 2) NOT NULL,
transaction_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT chk_amount CHECK (amount > 0), -- 트랜잭션 금액이 양수여야 함
CONSTRAINT fk_account
FOREIGN KEY (account_id)
REFERENCES accounts(account_id)
ON DELETE CASCADE
);
CREATE INDEX idx_account_transaction_date ON transactions (account_id, transaction_date);
transactions 테이블을 생성하고, CHECK 제약조건을 사용해 amount 필드가 양수만 허용되도록 설정했어요. 또한 account_id와 transaction_date에 인덱스를 추가하여 성능을 최적화할 수 있습니다. CHECK 제약조건이 있는 경우 특히 대용량 데이터에서 성능에 영향을 줄 수 있으므로, 인덱스를 통해 성능 저하를 방지할 수 있어요. 👍
유지보수를 고려해서 제약조건을 걸자!
마지막으로, 유지보수를 고려한 제약조건 설정도 중요해요. 데이터베이스는 시간이 지남에 따라 변경되고 확장될 가능성이 큽니다. 제약조건을 설정할 때, 나중에 이 제약조건이 유지보수 하기 쉬운지, 변경이 용이한지를 생각해야 해요. 예를 들어, 너무 복잡한 제약조건은 향후 요구사항이 변경될 때 수정이 어렵고, 오류를 일으킬 가능성이 있어요. 그렇기에 유지보수를 염두에 두고 최대한 간결하고 이해하기 쉬운 제약 조건을 설정해줘야 해요 🤔
CREATE TABLE promotions (
promotion_id SERIAL PRIMARY KEY,
promotion_name VARCHAR(100) NOT NULL,
discount_rate DECIMAL(5, 2) CHECK (discount_rate BETWEEN 0 AND 100), -- 할인율이 0~100% 사이여야 함
start_date DATE NOT NULL,
end_date DATE NOT NULL,
CHECK (end_date >= start_date) -- 종료일이 시작일보다 늦어야 함
);
promotions 테이블을 생성하고, discount_rate가 0에서 100 사이의 값인지 확인하는 CHECK 제약조건과 end_date가 start_date보다 크거나 같아야 한다는 조건을 설정했어요. 이 제약조건은 논리적으로 간결하면서도 명확하여, 나중에 유지보수할 때 수정이 용이합니다. 복잡하지 않으면서도 핵심적인 비즈니스 로직을 간결하게 표현한 제약조건은 유지보수를 쉽게 만들어줍니다.