Menu

SQLite 데이터 타입: 5가지 스토리지 클래스와 동적 타이핑

SQLite는 값을 어떻게 저장할까요? 5가지 스토리지 클래스부터 동적 타이핑의 원리, 그리고 Postgres·MySQL에서 넘어온 분들이 자주 걸리는 함정까지 정리했습니다.

이 페이지에는 실행 가능한 에디터가 있습니다 — 편집하고 실행하면 결과를 바로 볼 수 있습니다.

다섯 가지 스토리지 클래스가 전부다

SQLite는 모든 값을 다음 다섯 가지 스토리지 클래스(storage class) 중 하나로 저장합니다.

  • NULL — 값이 없음을 나타냅니다.
  • INTEGER — 부호 있는 정수로, 크기에 따라 1~8바이트를 차지합니다.
  • REAL — 8바이트 IEEE 부동소수점 숫자입니다.
  • TEXT — 문자열이며, 데이터베이스의 인코딩(보통 UTF-8)으로 저장됩니다.
  • BLOB — 입력한 바이트를 그대로 저장하는 원시 바이너리 데이터입니다.

이게 전부입니다. 별도의 BOOLEAN도, DATETIME도, VARCHAR도, DECIMAL도 없습니다. 다른 데이터베이스는 수십 가지 타입을 제공하지만, SQLite의 데이터 타입은 단 다섯 개뿐이고 나머지는 모두 이 위에서 구현됩니다.

typeof()를 쓰면 각 값이 실제로 어떤 스토리지 클래스에 저장돼 있는지 확인할 수 있습니다. 결과로는 integer, real, text, blob 중 하나가 나오는데, 여기에 null까지 더한 다섯 가지가 SQLite가 아는 타입의 전부입니다.

SQLite는 동적 타이핑이다

Postgres나 MySQL을 쓰다가 넘어온 분들이 가장 당황하는 지점이 바로 여기입니다. SQLite에서는 (STRICT 모드가 아닌 한) 컬럼에 선언한 타입이 강제 규칙이라기보다는 일종의 _권고사항_에 가깝습니다. 실제 타입은 컬럼이 아니라 값 하나하나에 따라붙어 있죠:

두 행 모두 정상적으로 들어갔습니다. id 컬럼에는 한 행은 정수가, 다른 행은 텍스트가 들어 있고 body는 텍스트와 정수가 섞여 있죠. SQLite는 어떤 컬럼에든 어떤 클래스의 값이든 군말 없이 저장합니다.

이게 바로 SQLite 동적 타이핑(dynamic typing)이고, 의도된 설계입니다. 덕분에 SQLite는 프로토타이핑이나 간단한 스크립트 작업에서 관대하게 동작합니다. 단점도 분명한데, 애플리케이션 코드의 사소한 오타 하나 때문에 엉뚱한 형태의 데이터가 몇 년 동안 조용히 쌓여 있을 수 있다는 겁니다. 이 트레이드오프가 마음에 걸린다면 — 그리고 운영 환경 스키마라면 당연히 걸려야 합니다 — 답은 STRICT 테이블입니다. SQLite STRICT 테이블은 곧 자세히 다룹니다.

한 문단으로 정리하는 타입 친화도(Type Affinity)

컬럼에 선언한 타입이 무시되는 건 아닙니다. 그 타입은 컬럼에 친화도(affinity) 를 부여합니다. 값을 INSERT하면 SQLite는 깔끔하게 변환할 수 있는 경우 컬럼의 친화도에 맞춰 값을 변환하려고 시도합니다. 예를 들어 TEXT 컬럼에 숫자 42를 넣으면 텍스트 '42'로 저장되고, INTEGER 컬럼에 문자열 '42'을 넣으면 정수 42로 저장됩니다. 변환 과정에서 정보가 손실될 것 같으면 원래 타입을 그대로 유지합니다.

첫 번째 행을 보면 정수 42는 텍스트 '42'로 변환됐고, 문자열 '100'은 정수 100으로 바뀌었습니다. 두 번째 행에서는 '3.5'를 손실 없이 INTEGER로 바꿀 수 없어서 그대로 텍스트로 남았죠. 어피니티(affinity)에 대한 자세한 내용은 별도 페이지에서 다룰 예정인데, 지금은 "컬럼 타입이 강제력은 없어도 저장 방식에는 여전히 영향을 준다"는 점만 기억해 두면 됩니다.

SQLite boolean 저장 방법

SQLite에는 BOOLEAN 스토리지 클래스가 따로 없습니다. 대신 boolean 값은 정수로 저장돼요 — 거짓이면 0, 참이면 1입니다:

TRUEFALSE 키워드는 SQLite 3.23부터 인식되며, 각각 10으로 변환됩니다. BOOLEAN으로 선언하면 컬럼에 numeric affinity가 부여될 뿐, 값을 0이나 1로 제한해 주지는 않습니다. 즉 STRICT 모드가 아니라면 'maybe' 같은 값을 넣어도 SQLite는 군말 없이 받아들입니다.

SQLite에서 날짜와 시간 저장하는 방법

DATETIME 타입도 따로 없습니다. 대신 아래 세 가지 인코딩 중 하나를 골라서 쓰면 되고, SQLite의 날짜 함수들은 셋 다 잘 처리해 줍니다.

  • ISO-8601 형식의 TEXT: '2026-04-23 14:30:00'.
  • 율리우스일(Julian day) 숫자를 담은 REAL.
  • 유닉스 에포크 초 단위의 INTEGER.

ISO-8601 형식의 문자열이 가장 무난한 선택입니다. 문자열로 정렬해도 시간 순서가 그대로 맞고, 사람이 읽기에도 편하며, 내장 함수인 date(), time(), datetime(), strftime(), julianday()가 모두 이 형식을 받아들이거든요. 한 컬럼에는 하나의 인코딩 방식만 정해서 쭉 밀고 가세요. 한 컬럼 안에 여러 포맷을 섞어 두면 6개월쯤 지났을 때 뒤통수를 제대로 맞게 됩니다.

VARCHAR, CHAR, 그 밖에 익숙한 타입 이름들

SQLite는 다른 데이터베이스에서 쓰던 타입 이름들을 그대로 받아줍니다. VARCHAR(255), CHAR(10), NVARCHAR, DECIMAL(10,2), DOUBLE, FLOAT, INT, BIGINT, MEDIUMINT 같은 것들 말이죠. 문법 오류 없이 잘 파싱됩니다. 다만 내부적으로는 타입 친화도(type affinity) 규칙에 따라 앞서 설명한 다섯 가지 스토리지 클래스 중 하나로 매핑될 뿐입니다.

VARCHAR(255)라고 써도 255자 제한이 걸리지 않습니다. SQLite는 길이 정보를 그냥 무시합니다. DECIMAL(10, 2) 역시 고정 정밀도 십진수로 저장되지 않습니다. NUMERIC 친화도(affinity)가 적용되어 결국 INTEGERREAL로 저장될 뿐이죠. 이런 타입 이름들은 단지 다른 DB에서 가져온 스키마가 그대로 실행되도록 호환성 차원에서 허용되는 것이고, 다른 DB에서 그 이름이 가지던 제약 조건까지 따라오는 건 아닙니다.

돈처럼 정확한 십진 연산이 필요하다면 센트 단위로 환산해서 INTEGER로 저장하세요. REAL(부동소수점)을 쓰면 언젠가는 소수점 셋째 자리 부근에서 반올림 오차가 슬그머니 끼어듭니다.

NULL도 하나의 스토리지 클래스다

NULL은 단순히 "값이 없음"이 아닙니다. 그 자체로 고유한 스토리지 클래스를 가진 값이며, typeof()로 확인하면 다음과 같이 나옵니다:

bnull로 표시됩니다. 이게 중요한 이유는 NULL은 그 어떤 값과도 같지 않기 때문이에요. 심지어 또 다른 NULL과도 같지 않습니다. b = NULL은 절대 참이 되지 않으며, 반드시 b IS NULL이라고 써야 합니다. 이 부분은 뒤에 나오는 연산자와 NULL 페이지에서 본격적으로 다루지만, 출발점은 바로 여기, 스토리지 클래스입니다.

SQLite BLOB 사용법: 바이트 그대로 저장하기

BLOB은 원시 바이트를 있는 그대로 저장합니다. 작은 이미지, 해시값, 인코딩된 데이터처럼 텍스트도 숫자도 아닌 데이터를 다룰 때 유용해요:

x'...' 리터럴을 사용하면 SQL에서 BLOB을 16진수로 직접 작성할 수 있습니다. 다만 애플리케이션 코드에서는 보통 파라미터로 바이트 배열을 넘기는 방식을 씁니다. BLOB에 length()를 적용하면 문자 수가 아니라 바이트 수가 반환된다는 점도 기억해 두세요.

실무 팁 하나. SQLite는 큰 BLOB도 무리 없이 저장하지만, 50MB짜리 BLOB이 들어 있는 행을 매번 쿼리할 때마다 끌어오면 속도가 크게 떨어집니다. 용량이 큰 파일은 디스크에 따로 저장하고, DB에는 경로만 남겨 두는 편이 낫습니다.

핵심 정리

  • SQLite 스토리지 클래스는 NULL, INTEGER, REAL, TEXT, BLOB 다섯 가지로 모든 값을 커버합니다.
  • boolean은 정수로 저장되고, 날짜·시간은 텍스트·실수·정수 중 원하는 방식으로 저장하면 됩니다.
  • 컬럼에 선언한 타입은 강제 규칙이 아니라 힌트일 뿐입니다(STRICT 테이블이 아닌 이상).
  • VARCHAR(255) 같은 표기는 문법상 허용되지만, 다른 DB에서처럼 길이나 정밀도를 강제하지는 않습니다.
  • 실제로 어떤 값이 들어 있는지 확인하고 싶을 땐 typeof(value)를 활용하세요.

다음 주제: 타입 친화도(Type Affinity)

방금 "힌트"라고 가볍게 짚고 넘어간 동작 방식 뒤에는 사실 정해진 규칙이 있습니다. 선언된 타입 이름에서 도출되는 다섯 가지 친화도 클래스가 있고, 값을 INSERT할 때마다 이 규칙이 적용되죠. 다음 페이지에서 자세히 다룰 예정인데, SQLite가 여러분이 넣은 값을 실제로 어떻게 처리할지 예측하는 데 가장 중요한 부분입니다.

자주 묻는 질문

SQLite에서 지원하는 데이터 타입은 어떤 게 있나요?

SQLite의 스토리지 클래스는 NULL, INTEGER, REAL, TEXT, BLOB 다섯 가지뿐입니다. 데이터베이스에 저장되는 모든 값은 이 다섯 가지 중 하나로 들어갑니다. VARCHAR(255), DATETIME, BOOLEAN 같이 익숙한 이름들은 호환성을 위해 CREATE TABLE에서 받아주긴 하지만, 실제 저장 시점에는 결국 이 다섯 개 중 하나로 매핑됩니다.

SQLite에는 boolean이나 datetime 타입이 따로 있나요?

별도의 스토리지 클래스로는 없습니다. 불리언은 INTEGER(0과 1)로 저장되고, 키워드 TRUEFALSE도 인식해 줍니다. 날짜·시간은 TEXT(ISO-8601 문자열), REAL(율리우스 적일), INTEGER(Unix epoch 초) 중 원하는 형식으로 저장하면 되고, SQLite의 날짜 함수들은 세 가지 모두에 대해 동작합니다.

SQLite의 타이핑이 '동적'이라고 불리는 이유는 뭔가요?

보통의 데이터베이스에서는 INTEGER로 선언된 컬럼에 문자열을 넣으려고 하면 거부합니다. 그런데 SQLite는 기본 동작에서 컬럼에 선언된 타입을 힌트 정도로만 보고, 실제 타입 정보는 각 값에 같이 따라다닙니다. 그래서 TEXT 컬럼에 정수를 그대로 넣을 수도 있죠. 이 유연함은 편리할 때도 있지만 발등을 찍는 경우도 많습니다. 엄격하게 막고 싶다면 STRICT 테이블을 쓰면 됩니다.

Coddy programming languages illustration

Coddy로 코딩 배우기

시작하기