스키마가 자주 바뀌는 속성이나 정형화되지 않은 메타데이터를 저장할 때, 컬럼을 매번 추가하는 대신 JSON 컬럼 하나에 담는 방법이 있습니다. MySQL 5.7부터 네이티브 JSON 타입을 지원하고, 8.0에서는 인덱싱까지 실용적인 수준이 됐습니다. 하지만 "편하다"는 이유로 남발하면 조회가 풀스캔으로 떨어지기 쉽습니다.
일반 컬럼과 JSON 컬럼, 무엇이 다른가
| 구분 | 일반 컬럼 | JSON 컬럼 |
|---|---|---|
| 스키마 | 고정, 변경 시 ALTER 필요 | 행마다 키 구성이 달라도 됨 |
| 조회 | 컬럼명으로 직접 | ->> 경로 추출 필요 |
| 인덱스 | 바로 가능 | 생성 컬럼을 거쳐야 함 |
| 검증 | 타입·제약으로 강제 | 값 형식을 애플리케이션이 책임 |
핵심은 조회 조건으로 자주 쓰는 값이면 일반 컬럼, 가끔 읽고 구조가 유동적이면 JSON이라는 점입니다.
저장과 조회
JSON 컬럼은 텍스트가 아니라 파싱된 바이너리로 저장되며, 경로 연산자로 값을 꺼냅니다.
SQL
CREATE TABLE products (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(200),
attrs JSON
);
INSERT INTO products (name, attrs)
VALUES ('노트북', '{"brand": "Acme", "ram_gb": 16, "tags": ["sale", "new"]}');
값 추출은 ->(JSON 반환)와 ->>(스칼라 텍스트 반환)를 구분해서 씁니다.
SQL
SELECT attrs->>'$.brand' AS brand,
attrs->'$.tags' AS tags
FROM products
WHERE attrs->>'$.brand' = 'Acme';
문제는 위 WHERE가 그대로는 인덱스를 못 타고 전체 행을 스캔한다는 것입니다.
인덱스를 태우는 법 — 생성 컬럼
자주 조회하는 JSON 경로는 생성 컬럼(generated column) 으로 꺼내 그 컬럼에 인덱스를 겁니다.
- 경로 값을 가상 컬럼으로 추출합니다.
SQL
ALTER TABLE products
ADD COLUMN brand VARCHAR(50)
GENERATED ALWAYS AS (attrs->>'$.brand') VIRTUAL;
- 생성 컬럼에 인덱스를 추가합니다.
SQL
ALTER TABLE products ADD INDEX idx_brand (brand);
- 이제 원래 JSON 조건으로 조회해도 옵티마이저가 인덱스를 사용합니다.
EXPLAIN에서key가idx_brand로 잡히는지 확인하세요. 배열 안 값을 찾을 때는JSON_CONTAINS나 8.0.17+ 의 멀티밸류 인덱스를 씁니다.
VIRTUAL은 저장 공간을 쓰지 않고 읽을 때 계산하며, 조회가 매우 빈번하면 STORED로 디스크에 저장해 계산 비용을 없앨 수 있습니다.
요점 정리
- JSON 컬럼은 유동적·비정형 데이터용이고, 조회 키로 쓰는 값은 일반 컬럼이 낫습니다.
- 값 추출은
->>(텍스트)와->(JSON)를 구분합니다. - 인덱스는 JSON 경로에 직접 못 걸고, 생성 컬럼을 거쳐 겁니다.
- 적용 후 반드시
EXPLAIN으로 인덱스 사용 여부를 확인합니다.
JSON 컬럼 설계와 생성 컬럼 인덱싱을 직접 실행해 보는 실습은 데이터베이스 트랙에서 회원가입 없이 무료로 할 수 있습니다.