Foreign Key 컬럼은 NULL을 가질 수 있는가

개념/배경 결론부터 말하면 가능함 Foreign Key 컬럼의 NULL 허용 여부는 해당 컬럼의 NULL 혹은 NOT NULL 제약으로 결정됨 FK 제약은 NULL이 아닌 값만 검증하므로 값이 NULL인 경우 참조 무결성 검사를 생략함 컬럼이 NOT NULL이면 NULL 저장 불가 컬럼이 NULL 허용이면 NULL 저장 가능 핵심 개념 FK 제약은 입력값이 존재할 때만 참조 대상 테이블에 키가 있는지 검사 NULL은 미지정 상태로 간주되어 검증 대상 아님 대부분의 RDBMS에서 기본은 NULL 허용 컬럼이며, 명시적으로 NOT NULL을 지정해야 함 동작 원리 INSERT 또는 UPDATE 시 FK 컬럼이 NULL이면 무결성 검사 스킵 값이 존재하면 참조 테이블의 대상 키 존재 여부 검사 복합 FK의 경우 참조 컬럼 중 하나라도 NULL이면 전체 FK 검사를 생략하는 동작이 일반적임 간단 예시 CREATE TABLE parent ( id INT PRIMARY KEY ); CREATE TABLE child ( parent_id INT, FOREIGN KEY (parent_id) REFERENCES parent(id) ); child.parent_id는 기본적으로 NULL 허용 상태 parent_id에 NULL을 넣어도 FK 위반 아님 parent_id에 NOT NULL을 추가하면 parent에 존재하는 id만 허용됨 주의 사항 ON DELETE SET NULL을 사용할 경우 FK 컬럼이 NULL 허용이어야 함 FK 컬럼 인덱스는 필수는 아니나 삭제·업데이트 시 성능과 잠금 범위 측면에서 권장 관계가 필수라면 애플리케이션 규칙이 아닌 스키마에서 NOT NULL로 모델링하는 편이 안전함 마무리 FK 컬럼은 NULL 허용 설정이면 NULL을 저장할 수 있으며, 이 경우 FK 검사는 수행되지 않음 NULL을 금지하고 참조 무결성을 강제하려면 NOT NULL을 함께 적용하면 됨 ...

February 19, 2026

CTE(Common Table Expression) 개념과 WITH 사용법 요약

개념/배경 CTE는 WITH로 정의하는 이름 있는 임시 결과셋 바로 다음 한 개 DML에서만 참조 가능하며 실행이 끝나면 범위 소멸 가독성 향상과 쿼리 구조 분리에 유용함 사용법 WITH cte_name (col1, col2) AS ( SELECT ... ) DML필요 시 컬럼 목록 생략 가능하나 명시를 권장 주의사항 일부 DBMS에서는 배치 내에서 CTE 앞에 오는 쿼리를 세미콜론으로 종료 필요 CTE 범위는 바로 뒤 한 개 DML로 한정됨 복잡한 쿼리에서 과도한 중첩 사용은 계획 복잡도 증가 가능 참고자료 https://s2choco.tistory.com/34 https://learn.microsoft.com/sql/t-sql/queries/with-common-table-expression-transact-sql

February 16, 2026

TypeORM JOIN vs 코드 레벨 매핑 선택 기준과 실무 트레이드오프

개요 동일한 문제를 TypeORM의 JOIN으로 해결할 수도 있고, 각 테이블을 개별 조회한 뒤 코드에서 매핑할 수도 있음 어떤 접근이 더 효율적인지는 데이터 크기, 관계 복잡도, 인덱스 상태, 네트워크 제약, 성능 요구사항에 따라 달라짐 핵심 장단점과 선택 기준을 정리함 TypeORM에서 JOIN 사용하는 경우 장점 단일 쿼리로 필요한 데이터 수집 가능, 왕복 횟수 감소로 지연시간 이점 DB가 JOIN과 실행계획을 최적화하는 경우 비용 최소화 기대 1:N, N:M 같은 관계 질의를 쿼리로 명시적으로 표현 가능 필터링, 정렬, 그룹화 등 집계성 처리에서 DB 연산 활용 용이 페이지네이션과 함께 일관된 결과를 만들기 수월함 단점 ...

January 30, 2026

데이터베이스 Collation 정렬·비교 규칙과 인코딩 차이

개요 Collation은 문자열 정렬과 비교 규칙 정의 테이블에 저장되는 바이트 표현은 인코딩(character set)으로 결정되며, 정렬과 비교 결과는 Collation으로 결정됨 둘은 목적과 적용 지점이 다름 핵심 개념 Character set은 문자 ↔ 바이트 매핑 Collation은 특정 문자 집합 위에서 정렬 순서와 동일성 판단 규칙 정의 대소문자 민감도(cs), 대소문자 비민감도(ci), 악센트 민감도(as), 악센트 비민감도(ai) 같은 속성으로 비교 엄격도 제어 언어별 규칙 존재하며 같은 인코딩이라도 Collation에 따라 결과 달라짐 동작 원리와 적용 범위 적용 레벨 계층 구조: 서버 기본값 → 데이터베이스 → 테이블 → 컬럼 → 표현식 단위 오버라이드 가능 영향 받는 연산: ORDER BY, GROUP BY, DISTINCT, LIKE, = 등 문자열 비교 연산 전반 인덱스는 해당 컬럼의 Collation 기준으로 구성되며 정렬 규칙이 범위 검색과 정렬 최적화에 직접 영향 다국어 환경에선 ICU 기반 Collation이나 언어 특화 Collation 선택 필요 ...

January 8, 2026

좋은 시스템 설계 가이드: 상태 최소화와 검증된 컴포넌트 조합의 원칙

개요 좋은 시스템 설계는 복잡해 보이지 않고 긴 시간 동안 별문제 없이 돌아가는 상태를 말함 핵심은 상태를 최소화하고, 검증된 단순한 컴포넌트를 필요할 때만 조합하는 방향 과설계나 과도한 신기술 도입은 문제를 감추거나 유지보수 비용을 키우는 경향 최소 기능의 단순 구조에서 시작 후 관측 기반으로 점진 개선 권장 시스템 설계의 정의와 접근 소프트웨어 설계가 코드 조립이라면 시스템 설계는 서비스를 조합하는 일이라는 관점 주요 구성 요소 팔레트 앱 서버 데이터베이스 캐시 큐와 잡 러너 이벤트 버스 프록시와 게이트웨이 좋은 설계의 징후 ...

January 1, 2026

VARCHAR(n) 길이 기준 정리 — 글자 수인가 바이트 수인가

개념/배경 VARCHAR(n)에서 n을 글자 수로 볼지 바이트 수로 볼지 혼동 많음 표준 SQL의 character varying(n)은 최대 글자 수 의미이나, 실제 구현은 DBMS와 문자셋 설정에 따라 달라짐 멀티바이트 문자셋에서는 저장 바이트 수와 글자 수가 다름. 길이 제한은 글자 수 기준일 수 있으나 내부 저장은 바이트 단위로 이뤄짐 DBMS별 동작 MySQL VARCHAR(n)에서 n은 글자 수 의미 utf8mb4 사용 시 글자 하나가 최대 4바이트까지 소요. 행 크기 제한 등으로 인해 저장 가능 여부는 바이트 한계에도 영향 받음 PostgreSQL character varying(n)에서 n은 글자 수 의미 저장은 바이트 단위이나 제약은 글자 수 기준으로 평가 SQL Server varchar(n)은 n이 바이트 수. 멀티바이트 문자 사용 시 같은 n이라도 담을 수 있는 글자 수 감소 nvarchar(n)은 n이 글자 수. 유니코드 2바이트 단위 저장. 글자 수 기준 제약 필요 시 nvarchar 사용 권장 Oracle VARCHAR2(n)은 기본이 바이트 기준. 세션/시스템에서 CHAR semantics 또는 컬럼 정의 시 VARCHAR2(n CHAR)로 명시하면 글자 수 기준 실무 팁 한글 100자, 영어 100자 모두 허용 기대라면 글자 수 기준 타입 필요 MySQL VARCHAR(100), PostgreSQL varchar(100), SQL Server에서는 nvarchar(100), Oracle에서는 VARCHAR2(100 CHAR) 선택 저장 바이트 한계 고려 필요. MySQL은 행 크기 한계, Oracle/SQL Server도 페이지 크기 등 제약 존재 길이 함수 차이 주의. 바이트 길이와 글자 길이 함수가 다른 경우 존재. 예를 들어 글자 길이 검증은 문자 길이 함수 사용 권장 이모지, 결합 문자 등 특수 유니코드 조합은 사용자 체감 글자 수와 코드 포인트 수가 다를 수 있음. 제품 요구사항에 맞는 길이 기준 정의 필요 정리 VARCHAR(n)이 항상 바이트 무관이라는 주장은 오해 많은 DBMS에서 n은 글자 수지만, SQL Server의 varchar처럼 바이트 기준인 구현 존재 문자셋과 저장 한계를 함께 고려해야 안정적인 길이 설계 가능 한글도 100글자, 영어도 100글자라는 기대를 보장하려면 글자 수 기준 타입과 설정을 명시적으로 선택할 것 참고자료 https://dev.mysql.com/doc/refman/8.0/en/char.html https://www.postgresql.org/docs/current/datatype-character.html https://learn.microsoft.com/sql/t-sql/data-types/char-and-varchar-transact-sql https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/Data-Types.html#GUID-3B0B0A24-FA05-4A1F-902E-2E6D0BF85673

December 5, 2025

Prisma에는 왜 JOIN이 없을까: ORM 패턴, 스키마, 내부 동작 정리

개요 Prisma로 관계형 데이터베이스를 다루다 보면 자연스럽게 드는 질문이 있음 Prisma에서는 JOIN이 어디로 갔나 하는 질문임 개발자가 작성하는 Prisma Client API에는 JOIN이 없고, 서브쿼리도 보이지 않음 정말로 없는지, 없다면 왜 그런지, 어떤 트레이드오프가 있는지 정리함 ORM이란 무엇인가 ORM은 객체와 관계형 데이터베이스 간의 매핑을 제공하는 아이디어이자 구현체 집합을 의미함 애플리케이션에서 모델을 통해 데이터베이스 테이블을 간접 제어하는 추상화 계층 제공 SQL을 직접 작성하지 않고 데이터 접근 로직을 일관된 API로 수행 가능 데이터베이스 의존성 완화 효과 기대 ...

November 23, 2025

Prisma where 관계 필터 some vs every 동작 차이와 주의점

관계형 데이터에서 Prisma의 where 절은 자식 레코드 기준으로 부모를 거르는 필터를 제공함. 특히 some과 every는 겉보기엔 비슷하지만 결과 집합을 크게 바꾸는 핵심 차이가 있음. 단일 필드만으로 필터링해도 동일하지 않을 수 있어 주의 필요 개념 some 관계된 레코드 중 적어도 하나가 조건을 만족하면 부모 포함 존재성 검사에 해당, 하나라도 매칭되면 true every 모든 관계된 레코드가 조건을 만족해야 부모 포함 단 하나라도 위배되면 제외됨 관계된 레코드가 하나도 없으면 vacuously true로 간주되어 조건 만족으로 처리됨 동작 원리 some은 존재량화, every는 전칭량화에 해당함 차이는 자식 레코드가 0개이거나 2개 이상일 때 두드러짐. 1개일 때는 조건이 동일하다면 결과가 같아질 수 있음 특히 자식이 없는 경우 every는 기본적으로 참으로 평가되어 부모가 포함됨. 빈 결과를 제외하려면 추가 조건 필요 간단 예시 model Post { id Int @id @default(autoincrement()) title String comments Comment[] } model Comment { id Int @id @default(autoincrement()) text String postId Int post Post @relation(fields: [postId], references: [id]) }댓글 text가 ‘interesting’인 항목을 기준으로 게시글을 거르는 케이스를 가정 ...

November 17, 2025

MySQL InnoDB 버퍼 풀 개념과 동작 원리, 크기 설정 가이드

개요 InnoDB 버퍼 풀은 데이터와 인덱스 페이지를 메모리에 캐싱하는 영역임 디스크 I/O를 획기적으로 줄여 지연 시간을 낮추는 게 목적임 InnoDB 스토리지 엔진(트랜잭션, MVCC, 행 단위 락 지원) 성능의 심장부라 할 수 있음 쉽게 말해, 자주 쓰는 데이터와 인덱스를 디스크 대신 메모리에 올려두고 처리하는 구조임 버퍼 풀 구성 요소 버퍼 풀에는 주로 이런 페이지(기본 16KB)가 올라옴 데이터 페이지: 실제 테이블 로우(Row)가 저장된 페이지 인덱스 페이지: B-Tree 인덱스 노드 페이지 (PK 및 세컨더리 인덱스 포함) 기타 관리 페이지: UNDO 페이지, 트랜잭션/MVCC 관리에 필요한 메타데이터 등 핵심 개념 페이지 캐싱 InnoDB는 디스크 데이터를 페이지 단위로 다룸 클라이언트가 특정 로우를 읽고 싶어 하면, 그 로우가 속한 페이지 전체를 버퍼 풀로 가져옴 이후 같은 페이지에 있는 다른 로우를 읽을 때는 디스크를 다시 보지 않고 버퍼 풀(메모리)에서 바로 조회함 ...

October 31, 2025

Prisma cursor 기반 페이지네이션 동작 원리와 skip: 1의 의미

개요 Prisma에서 cursor는 특정 레코드 지점부터 결과를 읽기 시작하는 기준점으로 동작함 skip: 1은 해당 cursor 레코드를 결과에서 제외하기 위한 옵션으로, 페이지 간 중복을 제거하는 데 사용함 핵심 동작 cursor는 그 지점부터 시작 await prisma.user.findMany({ cursor: { id: 100 }, take: 5, orderBy: { id: "asc" }, }); // 결과: 100부터 시작해 5개 반환 skip: 1은 cursor에 해당하는 레코드를 건너뜀 await prisma.user.findMany({ cursor: { id: 100 }, skip: 1, take: 5, orderBy: { id: "asc" }, }); // 결과: 101부터 5개 반환 예시로 보는 차이 데이터가 아래와 같다고 가정 ...

October 29, 2025