MySQL의 JSON 타입은 유연하지만 실무에서는 신중하게 사용해야 한다.
단순 저장엔 편리하지만, 검색, 수정, 인덱싱에서 예상 외의 문제가 자주 발생한다.

⚠️ 1. JSON은 인덱스를 직접 만들 수 없다
MySQL에서는 JSON 컬럼에 직접 인덱스를 걸 수 없다.
즉, JSON 내부 값을 조건으로 조회하면 풀스캔이 발생할 수 있다.
-- 아래 쿼리는 인덱스를 타지 않음
SELECT * FROM users
WHERE JSON_EXTRACT(profile, '$.nickname') = 'hong';
해결 방법은 생성 컬럼 + 인덱스 조합이다.
ALTER TABLE users
ADD COLUMN nickname VARCHAR(100)
GENERATED ALWAYS AS (JSON_UNQUOTE(profile->'$.nickname')) STORED,
ADD INDEX idx_nickname (nickname);
📦 2. 과도한 데이터 저장은 피하라
JSON 컬럼에 너무 많은 데이터를 담으면 오히려 복잡성과 성능 문제를 키운다.
아래처럼 user_info를 몽땅 JSON에 때려넣는 방식은 비추천이다.
-- 나쁜 예
user_info = {
"name": "지수",
"phone": "010-1234-5678",
"birth": "1994-05-12",
"marketing_optin": true
}
✅ 실무에서는 name, phone, birth 등은 별도 컬럼으로 분리하고
✅ marketing_optin 같이 부가 옵션만 JSON으로 보관하는 것이 좋다
🧩 3. JSON 구조가 불안정하면 코드도 깨진다
JSON은 스키마가 없기 때문에,
키가 누락되거나 타입이 바뀌면 프론트/백엔드 양쪽에서 오류가 날 수 있다.
-- 어떤 유저는 "age"가 있고
{ "name": "준호", "age": 30 }
-- 어떤 유저는 없음
{ "name": "소연" }
-- 어떤 유저는 타입도 다름
{ "name": "하나", "age": "서른" }
✅ 구조가 유동적인 경우엔 프론트 단에서 기본값 설정 or null 처리 필요
✅ 백엔드에서는 IFNULL, CAST 등으로 안정성을 확보하는 처리 필요
🧠 4. 복잡한 데이터는 별도 테이블로 분리하는 게 낫다
아래처럼 배열이나 중첩된 객체를 많이 쓰는 경우,
JSON보다 자식 테이블로 분리하는 게 훨씬 낫다.
-- JSON으로 넣은 예
{
"addresses": [
{ "type": "home", "addr": "서울시..." },
{ "type": "work", "addr": "성남시..." }
]
}
→ 이건 user_address라는 별도 테이블로 빼는 게 검색, 수정, JOIN 등 모든 면에서 유리하다
🧪 5. JSON 수정은 항상 신중하게
JSON_SET, JSON_REPLACE, JSON_INSERT 등으로 값을 바꿀 수 있지만
쿼리가 복잡해지고 오류 발생 가능성도 높다.
-- 실수로 key 중복되면 의도와 다르게 동작할 수 있음
SELECT JSON_SET('{"opt":true}', '$.opt', false);
-- 결과: {"opt": false}
✅ 기본값 설정은 INSERT 단계에서 미리 처리
✅ UPDATE 시에는 기존 JSON 값 추출 후 가공해서 다시 저장하는 방식 권장
🔒 6. 보안 이슈도 고려해야 한다
JSON 내부에 개인정보, 인증 토큰 등을 저장하면
로그, 에러 출력, 덤프 백업 등에 노출될 수 있다.
✅ JSON 안에 민감 정보는 절대 저장하지 말 것
✅ 로그 필터링, 접근 권한 관리도 필수
📌 요약 체크리스트
- 자주 조회되는 값은 별도 컬럼 + 인덱스
- 대용량/중첩 JSON은 자식 테이블로 분리
- 생성 컬럼으로 인덱싱 구조 확보
- 프론트/백엔드에서 구조 변화에 대비한 방어 코드 작성
- 민감 정보는 JSON에 저장하지 말기
MySQL의 JSON은 강력하지만 만능은 아니다.
적재적소에 쓰되, 검색/성능/유지보수 관점에서 구조를 잘 설계하는 것이 중요하다.
📘 공식 문서 참고
https://dev.mysql.com/doc/refman/8.0/en/json.html
'DB' 카테고리의 다른 글
| [MySQL] (GIS4️⃣) 공간 인덱스의 원리와 설정 방법 (1) | 2025.07.18 |
|---|---|
| [MySQL] (GIS3️⃣) POLYGON과 공간 포함관계 쿼리 실습 (4) | 2025.07.18 |
| [MySQL] (GIS2️⃣) 공간 데이터 저장 및 조회 실습: POINT, LINESTRING 활용법 (1) | 2025.07.18 |
| [MySQL] (GIS1️⃣) GIS란? 공간 데이터 타입(POINT, POLYGON 등) 완전 정리 (1) | 2025.07.18 |
| [MySQL] (JSON5️⃣) JSON 쿼리 성능 튜닝 전략: 인덱싱과 최적화 팁 (0) | 2025.07.17 |
| [MySQL] (JSON4️⃣) WHERE 절에서 JSON 조건 처리하는 방법 (0) | 2025.07.17 |
| [MySQL] (JSON3️⃣) JSON_EXTRACT, JSON_UNQUOTE, JSON_CONTAINS 파헤치기 (0) | 2025.07.17 |
| [MySQL] (JSON2️⃣) JSON_INSERT, JSON_SET, JSON_REPLACE 실전 활용법 (0) | 2025.07.17 |