infra
Platform

모듈 맵

[Database] 테이블, 스키마, 로우(Row), 컬럼(Column) 용어 완벽 가이드

0 / 37 완료

펼치기
0 / 37 완료0%

Database · 03 / 37

[Database] 테이블, 스키마, 로우(Row), 컬럼(Column) 용어 완벽 가이드

컬럼, 행, 테이블, 스키마, 데이터베이스의 계층 구조와 용어를 정확히 이해합니다

🚨INCIDENT ALERT
HIGH

테이블을 만드는 일은 컬럼 이름을 나열하는 것보다 훨씬 중요합니다. 제약조건, 기본값, NULL 허용 여부를 어떻게 두느냐에 따라 애플리케이션 버그를 DB가 막아주기도 하고 방치하기도 합니다. 스키마 기본기를 알면 데이터 품질을 처음부터 지킬 수 있습니다.

이번 챕터에서 배울 것

데이터베이스 구조의 계층을 명확히 이해하고, DDL을 사용해 테이블을 설계하고 수정하는 실전 능력을 갖춥니다.

  • 1Database > Schema > Table > Column > Row 계층 구조
  • 2PostgreSQL과 MySQL의 Schema 개념 차이
  • 3DDL: CREATE, ALTER, DROP
  • 4컬럼 제약조건 (NOT NULL, UNIQUE, DEFAULT, CHECK)
  • 5테이블 네이밍 컨벤션

테이블과 스키마 — 데이터베이스 구조 용어 완전 정리

스키마 설계 없이 기능부터 짜고 나중에 정리하면 된다고 생각했던 시절이 있다. users 테이블 하나에 컬럼을 계속 붙여가다 보니 어느 순간 user_extra_info, user_meta, user_data2 같은 컬럼이 수십 개 생겨났고, 어느 컬럼이 현재 쓰이는 건지 아무도 몰랐다. 거기다 분석팀이 같은 public 스키마에 stats 테이블을 잔뜩 쌓으면서, 특정 테이블 읽기 권한만 주려 해도 민감한 사용자 데이터까지 통째로 노출되는 구조가 돼버렸다. 처음부터 Database > Schema > Table > Column 계층을 이해하고 논리적으로 분리했다면 생기지 않았을 문제다. 이 모듈에서는 그 계층 구조의 의미와, DDL로 테이블을 안전하게 설계하고 수정하는 방법을 실무 관점에서 정리한다. "스키마가 뭐예요?"라는 질문에 MySQL 개발자와 PostgreSQL 개발자가 서로 다른 답을 하는 이유도 여기서 알게 된다.

데이터베이스를 처음 배울 때 가장 혼란스러운 것 중 하나가 용어입니다. "스키마가 데이터베이스야, 테이블이야?" "MySQL과 PostgreSQL에서 스키마의 의미가 다르다던데?" 이 모듈에서는 데이터베이스 구조의 계층과 각 용어의 정확한 의미를 정리하고, DDL로 테이블을 생성하고 수정하는 방법을 익힙니다.


💡개념

DB 구조 계층 — Database > Schema > Table > Column > Row

DBA가 보낸 접속 정보에 database, schema, table이 따로 있습니다. MySQL에서 쓰던 방식으로 PostgreSQL에 접속했더니 "schema not found"가 납니다. 계층 구조를 이해하지 못하면 "database가 없다"와 "table이 없다"를 같은 오류로 혼동하고, 어디를 어떻게 만들어야 하는지 알 수 없습니다.

DB 구조 계층 — Database > Schema > Table > Column > Row

5단계 계층 구조

관계형 데이터베이스는 아래 다이어그램과 같이 5단계 계층으로 구성됩니다. 가장 바깥쪽 Database Cluster는 PostgreSQL 서버 인스턴스 자체이고, 그 안에 여러 Database가 존재합니다. 각 Database 안에 Schema(네임스페이스)가 있고, Schema 안에 Table이 있습니다. Table은 열(Column)과 행(Row)으로 이루어진 2차원 구조입니다.

┌─────────────────────────────────────────────────┐
│  Database Cluster (서버 인스턴스)                  │
│  ┌──────────────────────────────────────────┐   │
│  │  Database: ecommerce                     │   │
│  │  ┌────────────────────────────────────┐  │   │
│  │  │  Schema: public                    │  │   │
│  │  │  ┌──────────────────────────────┐  │  │   │
│  │  │  │  Table: users                │  │  │   │
│  │  │  │  ┌────────┬───────┬────────┐ │  │  │   │
│  │  │  │  │ id(PK) │ email │  name  │ │  │  │   │
│  │  │  │  ├────────┼───────┼────────┤ │  │  │   │
│  │  │  │  │   1    │ a@b.c │  Alice │ │← Row │   │
│  │  │  │  │   2    │ c@d.e │   Bob  │ │  │  │   │
│  │  │  │  └────────┴───────┴────────┘ │  │  │   │
│  │  │  │          ↑ Column           │  │  │   │
│  │  │  └──────────────────────────────┘  │  │   │
│  │  │  Table: products, Table: orders... │  │   │
│  │  └────────────────────────────────────┘  │   │
│  │  Schema: analytics, Schema: audit...     │   │
│  └──────────────────────────────────────────┘   │
│  Database: warehouse, Database: logs...          │
└─────────────────────────────────────────────────┘

각 계층의 역할

계층설명PostgreSQL 예시
Database독립적인 데이터 컨테이너. 별도 DB 간 쿼리 불가ecommerce, warehouse
SchemaDB 내 논리적 네임스페이스. 테이블 그룹화public, analytics, audit
Table실제 데이터가 저장되는 2차원 구조users, products, orders
Column테이블의 세로축. 이름과 타입 정의id, email, created_at
Row테이블의 가로축. 실제 데이터 레코드특정 사용자 1명의 데이터

PostgreSQL vs MySQL — Schema 개념 차이

PostgreSQL과 MySQL은 Schema 개념이 완전히 다릅니다. PostgreSQL은 DatabaseSchema를 명확히 구분합니다. 하나의 Database 안에 여러 Schema가 공존할 수 있고, 각 Schema는 독립된 네임스페이스로 테이블을 격리합니다. 반면 MySQL/MariaDB에서는 DATABASESCHEMA가 완전히 같은 의미입니다. MySQL에는 PostgreSQL 방식의 중간 Schema 계층이 존재하지 않습니다.

SQL
\c ecommerce

CREATE SCHEMA analytics;

CREATE TABLE analytics.daily_stats (
    date   DATE PRIMARY KEY,
    visits INT
);

SELECT * FROM public.users;
SELECT * FROM users;
OUTPUT
실행 완료 또는 조회 결과가 표시됩니다.
🔍실행 후 확인할 것
  • 제약조건NOT NULL, UNIQUE, CHECK가 데이터 규칙을 막아주는지 확인합니다.
  • 기본값INSERT 시 누락 가능한 컬럼에 안전한 DEFAULT가 있는지 봅니다.
  • DDL 영향ALTER/DROP/TRUNCATE가 운영 테이블에 미치는 락과 복구 위험을 점검합니다.
SQL
CREATE DATABASE ecommerce;
CREATE SCHEMA ecommerce;

USE ecommerce;

Schema 활용 예시 — 멀티 테넌트 구조

SaaS 서비스에서 고객사별로 Schema를 분리하면, 동일한 Database에서 고객 데이터를 논리적으로 격리할 수 있습니다. search_path를 설정하면 쿼리에서 스키마 이름을 생략할 수 있어 코드 변경 없이 테넌트를 전환할 수 있습니다.

SQL
CREATE SCHEMA tenant_acme;
CREATE SCHEMA tenant_bigcorp;

CREATE TABLE tenant_acme.users (id SERIAL PRIMARY KEY, name VARCHAR(100));
CREATE TABLE tenant_bigcorp.users (id SERIAL PRIMARY KEY, name VARCHAR(100));

SET search_path = tenant_acme;
SELECT * FROM users;

개발 초기에 편의상 모든 테이블을 public 스키마에 집어넣었습니다. 서비스가 커지면서 분석팀, 백엔드팀, 감사(audit) 목적의 테이블이 public에 수백 개 섞이게 되었습니다. 분석팀에게 읽기 권한만 주려 해도 public에 있는 모든 테이블이 노출되는 문제가 발생했습니다. 민감한 users 테이블을 분석 쿼리에서 실수로 조회하는 사고도 발생했습니다.

원인: public 스키마는 기본적으로 모든 사용자에게 접근이 허용됩니다. 역할 분리 없이 테이블을 모두 public에 쌓으면 권한 제어가 불가능해집니다.

해결 방법: 초기 설계 단계에서 app, analytics, audit, internal 등의 스키마로 분리합니다. GRANT USAGE ON SCHEMA analytics TO analyst_role;처럼 스키마 단위로 권한을 부여하면 세밀한 접근 제어가 가능합니다. PostgreSQL 15+에서는 public 스키마의 기본 권한이 강화되었으므로 버전도 확인하세요.

💡개념

DDL로 테이블 만들기 — CREATE, ALTER, DROP

운영 서버에서 컬럼 하나를 추가하려는데 ALTER TABLE이 수백만 건 테이블에서 수 분간 락을 잡습니다. 더 나쁜 경우, DROP COLUMN을 잘못 치면 롤백이 안 됩니다. DDL은 DML과 달리 대부분 즉시 커밋되고, 실수의 복구 비용이 매우 높습니다. 각 DDL 명령어가 어떻게 동작하는지 정확히 알아야 스키마 변경을 안전하게 할 수 있습니다.

DDL로 테이블 만들기 — CREATE, ALTER, DROP

CREATE TABLE — 실전 예시

아래는 이커머스 상품 테이블의 완성도 높은 설계 예시입니다. 기본키에는 자동 증가 정수(SERIAL)를 사용하고, 외래키에는 삭제 정책을 명시합니다. 가격과 재고에는 CHECK 제약으로 음수를 방지하고, status 컬럼은 허용된 값 목록을 CHECK로 제한합니다. 자주 검색되는 category_idstatus 컬럼에는 별도 인덱스를 생성합니다.

SQL
CREATE TABLE products (
    id          SERIAL PRIMARY KEY,
    category_id INT NOT NULL
                REFERENCES categories(id)
                ON DELETE RESTRICT,
    name        VARCHAR(255) NOT NULL,
    slug        VARCHAR(255) UNIQUE NOT NULL,
    description TEXT,
    price       DECIMAL(12, 2) NOT NULL
                CHECK (price >= 0),
    stock       INT NOT NULL DEFAULT 0
                CHECK (stock >= 0),
    status      VARCHAR(20) NOT NULL DEFAULT 'active'
                CHECK (status IN ('active', 'inactive', 'discontinued')),
    created_at  TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at  TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE INDEX idx_products_category ON products(category_id);
CREATE INDEX idx_products_status   ON products(status);

주요 컬럼 제약조건

각 제약조건의 역할을 이해하고 적절히 조합하는 것이 중요합니다. NOT NULL은 필수 입력 필드를 보장하고, UNIQUE는 이메일처럼 중복 불가한 값을 관리합니다. DEFAULT는 생략 가능한 값에 자동으로 초기값을 넣어줍니다. CHECK는 비즈니스 규칙을 DB 레벨에서 강제합니다. NOT NULL + DEFAULT 조합이 가장 흔한 실전 패턴입니다.

SQL
email      VARCHAR(255) NOT NULL
email      VARCHAR(255) UNIQUE
created_at TIMESTAMP DEFAULT NOW()
is_active  BOOLEAN DEFAULT TRUE
age        INT CHECK (age >= 0 AND age <= 150)
price      DECIMAL CHECK (price > 0)
status     VARCHAR(20) CHECK (status IN ('active','inactive'))
stock      INT NOT NULL DEFAULT 0

ALTER TABLE — 테이블 구조 변경

운영 중인 테이블의 구조를 변경할 때는 작업 유형마다 위험도가 다릅니다. 새 컬럼 추가(ADD COLUMN)는 기존 데이터에 영향이 없어 안전합니다. 반면 컬럼 타입 변경이나 대용량 테이블에 NOT NULL 추가는 전체 테이블 재작성(Table Rewrite)을 유발해 서비스 중단을 일으킬 수 있습니다. NOT NULL 추가는 반드시 2단계(DEFAULT 설정 → NULL 제거)로 나눠 진행합니다.

위험 명령어

운영 데이터에 적용하면 되돌리기 어려운 변경입니다. 실행 전 대상 테이블, WHERE 조건, 백업 또는 롤백 경로를 반드시 확인하세요.

SQL
ALTER TABLE products
ADD COLUMN weight_kg DECIMAL(8, 3);

ALTER TABLE products
ADD COLUMN is_featured BOOLEAN NOT NULL DEFAULT FALSE;

ALTER TABLE products
RENAME COLUMN description TO product_description;

ALTER TABLE products
ALTER COLUMN price TYPE DECIMAL(15, 2);

ALTER TABLE products ALTER COLUMN weight_kg SET DEFAULT 0;
UPDATE products SET weight_kg = 0 WHERE weight_kg IS NULL;
ALTER TABLE products ALTER COLUMN weight_kg SET NOT NULL;

ALTER TABLE products DROP COLUMN weight_kg;

ALTER TABLE products ADD CONSTRAINT chk_price CHECK (price >= 0);
ALTER TABLE products DROP CONSTRAINT chk_price;

DROP 명령어 주의사항

DROP TABLE은 테이블과 모든 데이터를 복구 불가능하게 삭제합니다. CASCADE 옵션은 이 테이블을 FK로 참조하는 다른 테이블의 제약조건까지 함께 삭제하므로 특히 주의가 필요합니다. TRUNCATE는 구조는 유지하면서 데이터만 전체 삭제하며, DELETE보다 훨씬 빠릅니다.

위험 명령어

운영 데이터에 적용하면 되돌리기 어려운 변경입니다. 실행 전 대상 테이블, WHERE 조건, 백업 또는 롤백 경로를 반드시 확인하세요.

SQL
DROP TABLE products;

DROP TABLE products CASCADE;

DROP TABLE IF EXISTS products;

TRUNCATE TABLE products;
TRUNCATE TABLE products RESTART IDENTITY;

네이밍 컨벤션

일관된 네이밍 컨벤션은 팀 전체의 코드 가독성과 유지보수성을 높입니다. SQL 예약어(order, user, select)를 테이블명으로 사용하면 쿼리마다 따옴표로 감싸야 하므로 반드시 피합니다.

권장 규칙:
  테이블명:    복수형 스네이크케이스  → users, order_items, product_categories
  컬럼명:      단수형 스네이크케이스  → user_id, created_at, is_active
  PK:          id (또는 테이블명_id)  → id
  FK:          참조테이블_id          → user_id, category_id
  인덱스:      idx_테이블_컬럼        → idx_products_category_id
  제약조건:    chk_테이블_컬럼        → chk_products_price

피해야 할 것:
  ✗ 예약어 사용: order, user, select (따옴표로 감싸야 해서 불편)
  ✗ 대소문자 혼용: UserId, ProductName (DB마다 대소문자 처리 다름)
  ✗ 너무 짧은 이름: u, p, dt (가독성 저하)
💼
실무 맥락멀티테넌트 SaaS에서 schema per tenant 패턴
현업 패턴

B2B SaaS 서비스를 설계할 때 고객사 데이터 격리 방법으로 세 가지 선택지가 있습니다. 첫째, 테이블에 tenant_id 컬럼을 추가하는 방식(Row-level isolation)은 구현이 단순하지만 쿼리마다 WHERE tenant_id = ? 조건을 빠뜨리면 데이터 유출 사고로 이어집니다. 둘째, schema per tenant 패턴은 고객사마다 독립된 스키마를 만들어 완전히 격리하는 방식입니다. SET search_path = tenant_acme만 설정하면 기존 쿼리 코드를 수정하지 않아도 됩니다. 셋째, database per tenant는 격리 수준이 가장 높지만 DB 연결 수와 운영 비용이 급증합니다.

중소 규모 SaaS에서는 schema per tenant가 보안과 운영 비용의 균형이 좋습니다. PostgreSQL의 search_path 기능과 연결 풀러(PgBouncer)를 함께 사용하면 수백 개 테넌트도 효율적으로 관리할 수 있습니다.

다음 모듈에서는 숫자, 문자, 날짜 등 정밀한 데이터 타입 선택 기준과 잘못된 타입 선택이 가져오는 실무 문제를 다룹니다.

지식 확인

퀴즈 — 5문제

Q1

한 PostgreSQL 서버의 같은 데이터베이스를 analytics 팀과 backend 팀이 함께 쓰는데, 두 팀 모두 'users'라는 테이블이 필요하다. 이름 충돌 없이 공존시키려면?

Q2

MySQL에서 PostgreSQL로 마이그레이션하는 팀원이 'MySQL CREATE DATABASE = PostgreSQL CREATE SCHEMA이니까 명령을 바꿔야 해'라고 주장한다. 이 주장이 틀린 이유는?

Q3

ALTER TABLE 사용 시 가장 주의해야 할 작업은?

Q4

운영 중인 테이블(수백만 건)에 NOT NULL 컬럼을 추가해야 할 때 서비스 중단 없이 안전하게 처리하는 방법은?

Q5

컬럼에 DEFAULT 값을 설정하는 가장 중요한 이유는?

0 / 5 답변

🧪 실습으로 확인하기

PostgreSQL 설치 및 기본 설정

초급

Ubuntu 서버에 PostgreSQL을 설치하고, 데이터베이스와 사용자를 생성한 뒤 외부 접속이 가능하도록 설정한다.

40📋 5단계💻 직접 환경
실습 시작하기 →

이것도 배워보세요

database입문 · 45
[Database] PK, FK 제약조건과 Cascade 설정이 주는 영향
Database 트랙 계속
linux입문 · 30
[Linux] 개발자가 왜 리눅스 서버와 커맨드라인을 반드시 배워야 하는가
Linux 트랙 시작점