Skip to content
Back to skills

Yd Db Expert

ASecurity

엔진과 무관한 관계형 설계를 다룰 때 사용한다 — 스키마 정규화·키·NULL 의미·DB 제약·소프트 삭제·인덱스 선택 원칙·쿼리 규칙(N+1·플레이스홀더·커서 페이징)·트랜잭션 경계·마이그레이션 단계. PostgreSQL 고유 주제는 yd-pg-expert, Oracle 고유 주제는 yd-oracle-expert, SQLite 고유 주제는 yd-sqlite-expert 를 쓴다.

  • 12 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added October 4, 2026
ai-agentspythonbashsql

Security analysis

A100/100

Pro scans all 8 files and shows the line behind each finding

Scanned October 4, 2026

npx -y skills add LeeYudok/doksam-skills --skill yd-db-expert --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Yd Db Expert?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Yd Db Expert
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/leeyudok-yd-db-expert/badge)](https://www.skillsdirectory.com/skills/leeyudok-yd-db-expert)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: yd-db-expert
description: 엔진과 무관한 관계형 설계를 다룰 때 사용한다 — 스키마 정규화·키·NULL 의미·DB 제약·소프트 삭제·인덱스 선택 원칙·쿼리 규칙(N+1·플레이스홀더·커서 페이징)·트랜잭션 경계·마이그레이션 단계. PostgreSQL 고유 주제는 yd-pg-expert, Oracle 고유 주제는 yd-oracle-expert, SQLite 고유 주제는 yd-sqlite-expert 를 쓴다.
---

# yd-db-expert

엔진과 무관한 관계형 설계가 대상이다. 잠금·테이블 재작성·실행계획 읽는 법·타입 세부처럼
엔진마다 다른 동작은 엔진 스킬이 맡는다 — PostgreSQL 은 `yd-pg-expert`, Oracle 은
`yd-oracle-expert`, SQLite 파일은 `yd-sqlite-expert`. 애플리케이션 코드는 각 언어 스킬이 맡는다.

아래 단언 가운데 SQL 의미론에 관한 것은 `scripts/verify_db_claims.py` 가 SQLite 로
재현한다(8장). 같은 의미론은 표준 SQL 이라 PostgreSQL·Oracle 에서도 같다. 엔진마다 갈리는
지점은 본문에 적었다.

## 1. 스키마 설계 — 판단 기준

정규화는 목적이 아니라 **이상현상(anomaly)을 없애는 수단**이다. 3NF 를 기본으로 두고,
역정규화는 **측정된 병목**이 있을 때만, 그리고 **갱신 경로를 하나로 유지**할 수 있을 때만.

설계하기 전에 스스로 답한다:

1. **이 테이블의 한 행은 무엇 하나인가** — 한 문장으로 안 되면 쪼갤 신호다.
2. **자연키인가 대리키인가** — 사업자번호·사번처럼 외부가 소유한 값은 바뀐다.
   대리키(식별자)를 두고 자연키에는 유니크 제약을 건다.
3. **이 컬럼이 NULL 일 수 있는 실제 상황은 무엇인가** — 답이 없으면 `NOT NULL`.
   NULL 은 "모름"이지 "없음"이나 "0"이 아니다.
4. **삭제하면 무엇이 같이 사라져야 하는가** — FK 의 `ON DELETE` 를 의도적으로 정한다.
   기본값에 맡기지 않는다.

### 제약은 애플리케이션이 아니라 DB 에 건다

`NOT NULL`·`UNIQUE`·`CHECK`·`FOREIGN KEY` 는 마지막 방어선이다. 애플리케이션 검증은
사용자 경험용이고, 데이터 무결성은 DB 가 보장한다. **버그·수동 작업·다른 클라이언트는
애플리케이션을 우회한다.** 제약이 없는 테이블에 수동 SQL 세 줄을 넣으면 NULL·중복·음수 잔액이
그대로 들어가고, 제약이 있는 테이블은 셋 다 거부한다.

### NULL 의 의미론

- **`NULL = NULL` 은 참이 아니라 NULL 이다.** `WHERE col = NULL` 은 아무 행도 고르지 못한다.
  `IS NULL` 을 쓴다. `col <> 'x'` 도 NULL 인 행을 고르지 않는다.
- **`NOT IN` 서브쿼리에 NULL 이 하나라도 있으면 결과가 통째로 빈다.** 오류도 경고도 없다.
  서브쿼리 컬럼이 NULL 을 허용하면 `NOT EXISTS` 를 쓴다.
- **`UNIQUE` 는 NULL 끼리 같다고 보지 않는다.** NULL 행이 여러 개여도 통과한다. "값이 하나뿐"
  이 규칙이면 `NOT NULL` 을 함께 건다. NULL 도 하나만 허용하는 방법은 엔진마다 다르다.

### 시간과 통화

- 시각은 타임존이 붙은 형식으로 저장한다. 저장은 UTC, 표시에서 변환한다. 엔진별 타입 선택은
  엔진 스킬이 맡는다. 사용자 표기는 `YYYY-MM-DD HH:MM:SS.mmm` (KST 가정).
- 돈은 고정소수점(`numeric`/`decimal`)이나 최소 단위 정수(원·센트)로 저장한다. 부동소수점 금지.

### 소프트 삭제

`deleted_at` 을 도입하면 **모든 조회에 조건이 붙는다.** 빠뜨린 한 곳이 사고가 된다.
정말 필요하면 뷰나 행 수준 보안으로 강제하고, 아니면 이력 테이블로 옮기는 편이 낫다.

**일반 `UNIQUE` 는 탈퇴한 이메일의 재가입을 막는다.** 삭제 표시만 한 행이 값을 쥐고 있어서다.
살아 있는 행에만 거는 부분 유니크 인덱스(`WHERE deleted_at IS NULL`)는 재가입을 허용하고
활성 중복은 막는다. 부분 인덱스가 없는 엔진은 엔진 스킬에서 대안을 찾는다.

## 2. 인덱스

- **WHERE·JOIN·ORDER BY 에 쓰이는 컬럼**이 후보다. 전부 만들지 않는다 — 인덱스는
  쓰기 비용과 저장공간을 먹는다.
- 복합 인덱스는 앞 컬럼부터 쓰인다. 등호 조건 컬럼을 앞에, 범위 조건 컬럼을 뒤에 둔다.
- **FK 를 선언해도 자식 컬럼 인덱스는 생기지 않는다** (PostgreSQL·SQLite 실측). 부모를
  지우거나 부모 기준으로 자식을 찾을 때 자식 테이블 전체를 훑으므로 직접 만든다.
- **함수를 씌운 조건은 컬럼 인덱스를 못 탄다.** `WHERE lower(name) = ...` 는 `name` 인덱스를
  쓰지 않는다. 쿼리를 바꾸거나 표현식 인덱스를 만든다.
- 인덱스를 추가하기 전에 **쿼리를 고칠 수 있는지** 먼저 본다. 판단은 추측이 아니라 실행계획으로
  하고, 실행계획 읽는 법은 엔진 스킬을 따른다.

## 3. 쿼리

- `SELECT *` 를 애플리케이션 쿼리에 쓰지 않는다. 컬럼이 늘면 전송량이 늘고,
  의도치 않은 필드가 새어나간다.
- **N+1 을 의심한다.** 고객 50명의 주문 수를 건마다 세면 문이 51개 나간다. `IN` 한 번이면
  2개로 끝나고 결과는 같다. 목록을 돌며 조회하는 코드는 조인이나 `IN` 으로 바꾼다.
- **오프셋 페이징은 페이지 사이에 행이 들어오면 같은 행을 두 번 보여준다.** 큰 오프셋에서는
  느리기도 하다. 정렬 키 기준 커서(`WHERE id < ?`)를 쓴다.
- **커서의 정렬 키가 유일하지 않으면 동률 행을 건너뛴다.** 시각처럼 겹칠 수 있는 키는
  `(at, id) > (?, ?)` 처럼 유일한 키를 덧붙여 비교한다.
- 문자열 조립 금지. **값은 언제나 플레이스홀더.** 이어 붙인 `x' OR '1'='1` 은 전 행을 돌려주고,
  `O'Brien` 같은 정상 입력도 문법 오류를 낸다.
- **플레이스홀더는 값 자리에만 쓸 수 있다.** 테이블·컬럼 이름 자리에 넣으면 문법 오류다.
  식별자를 동적으로 넣어야 하면 화이트리스트로 검증하고 인용한다.

## 4. 트랜잭션

- **경계를 명시적으로 정한다.** "이 작업들이 전부 되거나 전부 안 돼야 한다"가 기준이다.
  이체의 입금과 출금을 따로 커밋하면 출금이 실패할 때 입금만 남아 돈이 생긴다.
- 트랜잭션 안에서 **외부 호출(HTTP·메일)을 하지 않는다.** 락을 잡은 채 네트워크를 기다린다.
- 격리수준은 기본으로 두고, 필요한 경우에만 올린다. 올리면 직렬화 실패 재시도가 짝이다.
  엔진마다 기본값과 오류 코드가 다르므로 엔진 스킬을 따른다.
- 락 순서를 일정하게 유지해 교착을 피한다.
- 긴 트랜잭션을 만들지 않는다. 배치는 잘라서 커밋한다. 엔진마다 대가는 다르다 — 정리 작업
  지연, 잠금 대기, 언두 공간 부족.

## 5. 마이그레이션

- **되돌릴 수 있게** 쓴다. 되돌릴 수 없으면(데이터 삭제) PR 본문에 명시한다.
- 운영 중 스키마 변경은 **잠금 시간**이 관건이다. 어떤 변경이 테이블을 다시 쓰고 무엇을
  막는지는 엔진과 버전마다 다르므로 엔진 스킬의 표로 확인한다. 기억으로 판단하지 않는다.
- 큰 테이블은 단계를 나눈다: 컬럼 추가 → 백필(배치) → 제약 추가 → 구 컬럼 제거.
  각 단계가 따로 배포되어도 애플리케이션이 동작해야 한다.
- 적용 전 **백업 또는 되돌릴 계획**을 확인한다.

## 6. 하지 않는 것

- 특정 엔진의 운영(접속·커넥션 상한·진단 쿼리)은 다루지 않는다. doksam pig 공유 PostgreSQL
  클러스터 운영은 `yd-pg-expert` 가 맡는다.
- 엔진별 타입·잠금·실행계획 세부를 여기에 적지 않는다. 적으면 엔진 스킬과 어긋난다.

## 7. 완료 조건

- 새 테이블·컬럼에 적절한 제약(`NOT NULL`·FK·`UNIQUE`)이 있고, NULL 허용은 근거가 있다.
- NULL 을 허용하는 컬럼을 `NOT IN`·`=` 로 비교하는 곳이 없다.
- FK 컬럼과 조회 조건에 인덱스가 있고, 느린 쿼리는 엔진 스킬대로 실행계획을 확인했다.
- 목록 조회에 N+1 이 없고, 페이징은 유일한 키 기준 커서다.
- 마이그레이션이 되돌릴 수 있거나, 불가능함을 명시했다. 잠금 영향은 엔진 스킬로 확인했다.
- 시크릿이 출력·로그·이슈에 노출되지 않았다.

## 8. 이 문서의 단언 재현

```bash
python3 skills/yd-db-expert/scripts/verify_db_claims.py
```

NULL 비교, `NOT IN`, `UNIQUE` 와 NULL, DB 제약, FK 인덱스, 함수 조건, N+1, 오프셋·커서
페이징, 원자성, 플레이스홀더, 소프트 삭제 유니크를 stdlib `sqlite3` 로 재현한다. 하나라도
문서와 다르면 exit 1 이다. 단언과 테스트의 대응은 `tests/claims.json` 이 갖는다.

Files in this skill

  • SKILL.md8.8 KB
  • agents/antigravity.md1 KB
  • agents/claude.md656 B
  • agents/codex.toml618 B
  • agents/openai.yaml282 B
  • scripts/verify_db_claims.py15.6 KB
  • tests/claims.json2.5 KB
  • tests/test_db_claims.py2.6 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.