SQLite 타입 오류를 일찍 막는 STRICT 테이블 사용법

SQLite는 열에 `INTEGER`를 선언해도 일반 테이블이라면 텍스트를 저장할 수 있어요. 이 유연함이 편할 때도 있지만, 예상하지 못한 값이 조용히 쌓이면 오류를 발견하는 시점이 늦어져요. SQLite 3.37.0 이상에서는 테이블 정의 끝에 `STRICT`를 붙여 이런 타입 불일치를 저장 단계에서 막을 수 있어요. 1
핵심 요약
| 구분 | 내용 | 개발할 때 확인할 점 |
| 적용 방법 | `CREATE TABLE` 정의 끝에 `STRICT`를 붙여요 | SQLite 3.37.0 이상이 필요해요 |
| 타입 검사 | 삽입과 갱신 때 열 타입을 확인해요 | 정수 열에 임의의 텍스트가 들어가는 실수를 막아요 |
| 허용 타입 | `INT`, `INTEGER`, `REAL`, `TEXT`, `BLOB`, `ANY`를 쓸 수 있어요 | `VARCHAR`, `DATETIME`, `JSON` 같은 이름은 그대로 쓸 수 없어요 |
| 유연한 열 | 여러 타입이 필요한 열에는 `ANY`를 지정해요 | 예외가 필요한 열만 좁게 열어 두는 편이 안전해요 |
| 기존 DB 전환 | 새 STRICT 테이블을 만들고 데이터를 옮겨요 | 복사 전에 기존 타입 오류를 찾아 정리해야 해요 |
1. STRICT를 붙이면 SQLite의 타입 오류를 저장 단계에서 잡아요
일반 SQLite 테이블은 타입 선호도(type affinity)를 사용해요. 열에 `INTEGER`를 적어도 변환할 수 없는 텍스트가 들어갈 수 있어요. 앱이 값을 다시 읽을 때 숫자라고 가정하면 계산, 정렬, 직렬화 과정에서 뒤늦게 문제가 드러나요.
STRICT 테이블은 삽입과 갱신 시점에 값을 검사해요. 예를 들어 `age INTEGER` 열에 `'garbage'`를 넣으면 오류가 나요. 잘못된 값이 데이터베이스에 남기 전에 실패하므로 오류가 발생한 코드 경로를 찾기도 쉬워져요. 2
```sql CREATE TABLE people ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, age INTEGER ) STRICT; ```
검사가 모든 문자열을 무조건 거부하는 것은 아니에요. `'123'`처럼 정보 손실 없이 정수로 바꿀 수 있는 값은 `INTEGER` 열에 저장할 수 있어요. 반면 숫자로 바꿀 수 없는 문자열은 거부해요. 입력 경계에서 문자열로 받은 숫자를 다루면서도 명백한 타입 오류는 차단할 수 있는 방식이에요.
STRICT 테이블에서 선언할 수 있는 열 타입은 `INT`, `INTEGER`, `REAL`, `TEXT`, `BLOB`, `ANY`로 제한돼요. 평소 다른 데이터베이스에서 쓰던 `VARCHAR`, `DATETIME`, `JSON`, `UUID` 같은 타입 이름을 그대로 적으면 테이블 생성 단계에서 오류가 나요. 날짜는 정해진 형식의 `TEXT`나 숫자형 타임스탬프로 저장하고, JSON은 `TEXT`에 저장한 뒤 JSON 함수와 별도 제약 조건을 조합하는 식으로 스키마를 명확히 정해야 해요.
여러 타입이 꼭 필요하면 ANY를 써요
한 열에 정수와 문자열을 함께 보관해야 하는 요구가 있다면 해당 열을 `ANY`로 선언할 수 있어요. 테이블 전체의 타입 검사를 포기하지 않고 예외가 필요한 열만 유연하게 둘 수 있어요.
`ANY`는 기본 선택지보다 탈출구에 가까워요. 어떤 값이 들어오는지 애플리케이션 코드와 문서에서 따로 관리하지 않으면 읽는 쪽의 분기 처리가 늘어나요. 먼저 `TEXT`, `INTEGER`, `BLOB` 가운데 하나로 모델링할 수 있는지 확인하고, 여러 타입 자체가 데이터 의미의 일부일 때만 `ANY`를 쓰는 편이 좋아요.
기존 테이블은 새로 만들고 옮겨야 해요
이미 운영 중인 일반 테이블에 설정 하나만 바꿔 STRICT를 적용할 수는 없어요. 새 STRICT 테이블을 만든 뒤 기존 데이터를 복사하고, 테이블 이름과 인덱스·트리거를 정리해야 해요.
마이그레이션 전에 열별 실제 저장 타입을 확인하면 복사 중단을 줄일 수 있어요. `typeof()`로 선언과 다른 값이 있는지 찾고, 변환 가능한 값은 명시적으로 캐스팅해요. 잘못된 값은 삭제할지 보정할지 제품 규칙에 따라 결정해야 해요.
```sql SELECT id, age, typeof(age) FROM people WHERE age IS NOT NULL AND typeof(age) != 'integer'; ```
데이터 복사는 트랜잭션 안에서 진행하고, 새 테이블의 행 수와 주요 집계값을 원본과 비교하는 편이 안전해요. 외래 키, 인덱스, 트리거가 빠지지 않았는지도 함께 확인해야 해요. 배포 대상 기기에 포함된 SQLite 버전이 3.37.0보다 낮다면 STRICT 구문을 읽지 못하므로 앱의 최소 지원 버전도 먼저 점검해야 해요.
왜 중요한가요
SQLite는 모바일 앱, 데스크톱 앱, 로컬 우선 도구, 테스트 환경에서 자주 쓰여요. 이런 환경에서는 별도 데이터베이스 서버가 스키마 오류를 대신 잡아주지 않아요. 저장 계층에서 타입 불일치를 막으면 잘못된 상태가 백업이나 동기화 데이터로 퍼지는 범위를 줄일 수 있어요.
새 프로젝트라면 STRICT 테이블을 기본값으로 검토할 만해요. 스키마에 허용할 값이 분명해지고, 타입 이름 오타도 테이블 생성 때 발견할 수 있어요. 기존 프로젝트는 전체 데이터베이스를 한 번에 바꾸기보다 오류 비용이 큰 테이블부터 옮기는 방식이 현실적이에요. 결제 금액, 수량, 상태 코드처럼 타입이 바뀌면 계산과 분기에 직접 영향을 주는 열을 먼저 확인해 보세요.
성능 비용은 쓰기 때 타입을 확인하는 만큼 이론적으로 존재해요. 원문 필자가 진행한 비공식 실험에서는 뚜렷한 속도나 파일 크기 차이를 찾지 못했지만, 모든 서비스의 부하를 대신 설명하는 결과는 아니에요. 대량 쓰기가 많은 앱이라면 실제 데이터와 쿼리로 전환 전후를 측정해야 해요. 2
참고 자료
- SQLite에서는 STRICT 테이블을 우선 사용하라 — GeekNews
- Prefer STRICT tables in SQLite — Evan Hahn
'IT & AI' 카테고리의 다른 글
| AI 에이전트 SaaS, 도구보다 반복 업무를 먼저 골라야 해요 (0) | 2026.07.13 |
|---|---|
| AI 답변 한 줄이 데이터센터를 통과하는 15단계 (0) | 2026.07.13 |
| AI가 코드를 짜도 배포 책임은 엔지니어에게 남아요 (0) | 2026.07.13 |
| 코딩 에이전트 기록을 한곳에서 찾는 AgentsView, 비용과 변경 파일까지 보여줘요 (0) | 2026.07.13 |
| Lisp가 뛰어나도 주류가 되지 못한 이유, 코딩보다 마무리가 어려웠어요 (0) | 2026.07.12 |