deep-dive2025-02-20·9 min·184/348

MySQL JSON 데이터 타입 활용: 비정형 데이터 처리

MySQL JSON 데이터 타입의 활용 방법과 성능 고려사항을 설명합니다.

MySQL JSON 데이터 타입 활용

Introduction

MySQL 5.7부터 도입된 JSON 데이터 타입은 비정형 데이터를 효과적으로 처리할 수 있게 해줍니다. 관계형 데이터베이스의 고정된 스키마 제약을 넘어서 동적인 데이터 구조를 저장하고 조회할 수 있으며, JSON 함수들을 통해 효율적인 데이터 추출이 가능합니다. 이 글에서는 JSON 데이터 타입의 활용 방법과 최적화 기법을 다루겠습니다.

Environment

-- MySQL 버전 확인 (5.7 이상 필요)
SELECT VERSION();

-- JSON 데이터 타입 지원 확인
SELECT JSON_TYPE('"test"');
-- STRING
# MySQL JSON 함수 테스트
mysql -u root -p -e "SELECT JSON_EXTRACT('{\"name\": \"joel\"}', '$.name');"

Problem

JSON 데이터 처리 시 발생하는 문제들:

-- JSON 데이터 삽입
CREATE TABLE products (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(255),
    attributes JSON
);

INSERT INTO products (name, attributes) VALUES
('스마트폰', '{"brand": "Samsung", "color": "black", "specs": {"ram": "8GB", "storage": "256GB"}}');

-- JSON 경로 조회 시 성능 문제
SELECT * FROM products 
WHERE JSON_EXTRACT(attributes, '$.brand') = 'Samsung';
-- 인덱스 미사용으로 전체 테이블 스캔

-- JSON 배열 처리의 복잡성
SELECT * FROM products 
WHERE JSON_CONTAINS(attributes->'$.tags', '"premium"');
# JSON 함수 실행 시간 측정
mysql -u root -p -e "
EXPLAIN SELECT * FROM products 
WHERE JSON_EXTRACT(attributes, '$.brand') = 'Samsung';
"
-- type: ALL (전체 스캔)
-- key: NULL (인덱스 미사용)

Analysis

JSON 데이터 처리 성능 문제를 분석했습니다:

-- JSON 데이터 분포 확인
SELECT 
    JSON_EXTRACT(attributes, '$.brand') as brand,
    COUNT(*) as count
FROM products
GROUP BY brand
ORDER BY count DESC
LIMIT 10;

-- JSON 데이터 크기 분석
SELECT 
    AVG(LENGTH(attributes)) as avg_json_size,
    MAX(LENGTH(attributes)) as max_json_size,
    COUNT(*) as total_rows
FROM products;
-- avg_json_size: 156
-- max_json_size: 1024
-- total_rows: 1000000

-- JSON 쿼리 실행 계획 분석
EXPLAIN FORMAT=JSON
SELECT * FROM products 
WHERE JSON_EXTRACT(attributes, '$.brand') = 'Samsung';

Solution

1단계: Generated Column과 인덱스

-- Generated Column을 사용한 JSON 인덱싱
ALTER TABLE products 
ADD COLUMN brand VARCHAR(100) 
GENERATED ALWAYS AS (JSON_UNQUOTE(JSON_EXTRACT(attributes, '$.brand'))) STORED;

-- 인덱스 생성
CREATE INDEX idx_products_brand ON products(brand);

-- 이제 인덱스가 사용됨
EXPLAIN SELECT * FROM products WHERE brand = 'Samsung';
-- type: ref
-- key: idx_products_brand
-- rows: 150000

-- 복합 Generated Column
ALTER TABLE products 
ADD COLUMN ram VARCHAR(20) 
GENERATED ALWAYS AS (JSON_UNQUOTE(JSON_EXTRACT(attributes, '$.specs.ram'))) STORED;

ALTER TABLE products 
ADD COLUMN storage VARCHAR(20) 
GENERATED ALWAYS AS (JSON_UNQUOTE(JSON_EXTRACT(attributes, '$.specs.storage'))) STORED;

CREATE INDEX idx_products_specs ON products(ram, storage);

2단계: JSON 함수 최적화

-- JSONPath 표현식 최적화
SELECT 
    id,
    name,
    attributes->>'$.brand' as brand,
    attributes->>'$.color' as color,
    attributes->'$.specs'->>'$.ram' as ram
FROM products
WHERE attributes->>'$.brand' = 'Samsung'
  AND attributes->'$.specs'->>'$.ram' = '8GB';

-- JSON 배열 함수 활용
SELECT 
    id,
    name,
    JSON_LENGTH(attributes->'$.tags') as tag_count
FROM products
WHERE JSON_CONTAINS(attributes->'$.tags', '["premium", "new"]');

-- JSON 테이블 함수 (MySQL 8.0+)
SELECT 
    p.id,
    p.name,
    jt.*
FROM products p,
JSON_TABLE(
    p.attributes->'$.tags',
    '$[*]' COLUMNS (
        tag VARCHAR(50) PATH '$'
    )
) jt
WHERE jt.tag = 'premium';

3단계: JSON 데이터 구조 설계

-- JSON 데이터 구조 최적화
CREATE TABLE optimized_products (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(255),
    brand VARCHAR(100),
    category VARCHAR(50),
    base_price DECIMAL(10,2),
    -- 변형 속성만 JSON으로 저장
    variant_attributes JSON,
    -- 정적 인덱스 컬럼
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX idx_brand (brand),
    INDEX idx_category (category)
);

-- 데이터 삽입
INSERT INTO optimized_products (name, brand, category, base_price, variant_attributes) VALUES
('갤럭시 S24', 'Samsung', '스마트폰', 1200000, '{"color": "black", "storage": "256GB", "ram": "8GB"}');

-- JSON 데이터와 정적 컬럼 결합 조회
SELECT 
    id,
    name,
    brand,
    base_price,
    variant_attributes->>'$.color' as color,
    variant_attributes->>'$.storage' as storage
FROM optimized_products
WHERE brand = 'Samsung'
  AND base_price > 1000000;

Lessons Learned

  1. Generated Column 활용: 빈번하게 조회되는 JSON 필드는 Generated Column으로 변환하여 인덱싱해야 합니다
  2. 스키마 설계: 모든 데이터를 JSON에 넣지 말고, 정적 필드는 별도 컬럼으로 분리하는 것이 성능에 유리합니다
  3. JSON 함수 선택: JSON_EXTRACT보다 ->> 연산자가 더 효율적입니다
  4. 배치 처리: 대량의 JSON 데이터 삽입 시 배치 처리를 사용하면 성능이 향상됩니다
  5. MySQL 8.0 기능 활용: JSON_TABLE, JSON_ARRAYAGG 등의 함수를 활용하면 복잡한 JSON 처리가 쉬워집니다

이 블로그는 외부 스폰서십, 제휴 마케팅 또는 광고 수익을 받지 않습니다.