데이터베이스 용어 사전
제약·뷰VIEW · 구체화 뷰 · MATERIALIZED VIEW

쿼리에 이름을 붙여 가상 테이블처럼 쓰는 것. 구체화 뷰는 결과를 실제로 저장한다.

쿼리에 이름을 붙여 테이블처럼 쓰게 하는 객체. 데이터를 저장하지 않는다.

일반 뷰 — 저장하지 않는다

CREATE VIEW active_member AS
SELECT id, name, email FROM member WHERE deleted_at IS NULL;

SELECT * FROM active_member WHERE email = 'a@b.com';
-- 실행 시 뷰 정의가 쿼리에 합쳐진다(인라인). 별도 저장 공간이 없다

용도 세 가지

  • ① 복잡한 쿼리를 이름으로 감춘다 (가독성·재사용)
  • ② 권한 통제 — 민감 칼럼을 뺀 뷰만 조회 권한을 준다
  • ③ 스키마 변경을 흡수한다 — 테이블을 바꿔도 뷰 정의만 고치면 사용처가 안 바뀐다

권한 통제 예시

CREATE VIEW member_public AS SELECT id, name FROM member;   -- 주민번호·전화 제외
REVOKE ALL ON member FROM analyst;
GRANT SELECT ON member_public TO analyst;

구체화 뷰 — 결과를 실제로 저장한다

CREATE MATERIALIZED VIEW daily_sales AS
SELECT date(created_at) AS d, sum(amount) AS total FROM orders GROUP BY 1;

CREATE UNIQUE INDEX ON daily_sales (d);        -- CONCURRENTLY 갱신에 필수
REFRESH MATERIALIZED VIEW CONCURRENTLY daily_sales;
  • 일반 뷰 — 항상 최신. 매번 계산한다 → 무거운 집계면 느리다
  • 구체화 뷰 — 미리 계산해 저장한다 → 조회가 빠르다. 대신 갱신 전까지 낡은다

CONCURRENTLY 없이 갱신하면 그동안 조회가 막힌다 — 운영에서 자주 데는 지점이고, 쓰려면 유니크 인덱스가 미리 있어야 한다.

MySQL에는 구체화 뷰가 없어 요약 테이블 + 배치로 직접 만든다.

갱신 가능한 뷰

-- 단순한 뷰는 INSERT/UPDATE 가 원본 테이블로 전달된다
UPDATE active_member SET name = '홍길동' WHERE id = 1;   -- member 가 갱신된다

-- 조인·집계·DISTINCT 가 들어가면 불가능하다 → INSTEAD OF 트리거로 직접 구현

성능상의 함정

-- 뷰를 뷰 위에 여러 겹 쌓으면 옵티마이저가 감당하지 못한다
CREATE VIEW v3 AS SELECT * FROM v2 JOIN ...;   -- v2 는 v1 위에, v1 은 또 조인 위에
-- → 계획이 폭발하거나 불필요한 테이블까지 읽는다

뷰는 저장 프로시저가 아니라 쿼리 텍스트의 치환에 가깝다. 겹치면 그만큼 큰 쿼리가 된다.

면접 함정

  • "뷰를 쓰면 빨라진다" → 일반 뷰는 성능과 무관하다. 매번 계산한다.
  • "뷰는 읽기 전용" → 단순 뷰는 갱신 가능하다.

뷰가 성능을 망치는 실제 사례

-- 뷰 안에 이미 집계가 있는데 바깥에서 한 건만 찾는다
CREATE VIEW v_member_stat AS
SELECT member_id, count(*) cnt, sum(amount) total FROM orders GROUP BY member_id;

SELECT * FROM v_member_stat WHERE member_id = 42;
-- 옵티마이저가 조건을 안으로 밀어 넣지 못하면
-- → 전체 회원을 집계한 뒤 하나만 골라낸다
EXPLAIN ANALYZE SELECT * FROM v_member_stat WHERE member_id = 42;
-- GroupAggregate ... rows=1000000   ← 조건이 안 밀려 들어갔다는 신호

조건이 밀려 들어가는지(predicate pushdown) 계획으로 확인하는 것이 뷰를 쓸 때의 필수 점검이다.

뷰 정의를 바꿀 때

CREATE OR REPLACE VIEW v AS SELECT ...;   -- 칼럼 개수·이름·타입이 같아야 한다
DROP VIEW v; CREATE VIEW v AS ...;        -- 구조가 바뀌면 이렇게. 의존 객체가 깨진다

-- 이 뷰에 무엇이 의존하고 있나
SELECT dependent_ns.nspname, dependent_view.relname
  FROM pg_depend d JOIN pg_rewrite r ON r.oid = d.objid
  JOIN pg_class dependent_view ON dependent_view.oid = r.ev_class
  JOIN pg_namespace dependent_ns ON dependent_ns.oid = dependent_view.relnamespace
 WHERE d.refobjid = 'v'::regclass;

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — 제약·트리거·뷰 — DB가 지키는 규칙·가상 테이블