PostgreSQL vs MySQL 선택 기준 — 2026년 신규 프로젝트 실전 비교

목차

PostgreSQL과 MySQL, 2026년 기준으로 다시 본다

PostgreSQL은 객체-관계형 데이터베이스로, 표준 SQL 준수율이 높고 JSON, 배열, 범위 타입 같은 고급 데이터 타입을 네이티브로 지원한다. MySQL은 관계형 데이터베이스의 대표 주자로, 단순 읽기 중심 워크로드에서 빠른 처리 속도를 보여준다. 둘 다 오픈소스이고, 둘 다 2026년 현재 활발하게 업데이트되고 있다.

PostgreSQL vs MySQL 선택 기준을 검색하면 "PostgreSQL은 복잡한 쿼리에 강하고 MySQL은 단순 CRUD에 빠르다"는 문장이 반복된다. 이 말 자체는 틀리지 않다. 그런데 이걸 기준으로 DB를 고르면 실제 프로젝트에서 후회할 확률이 높다. MySQL 8.4에서 JSON 지원이 꽤 개선됐고, PostgreSQL 17에서는 성능 최적화가 많이 들어갔기 때문이다. "예전 버전 기준의 상식"으로 결정하면 안 된다는 얘기다.

이 글은 새 프로젝트에서 두 DB를 직접 테스트하면서 느낀 차이를 정리한 것이다. 벤치마크 숫자를 지어내진 않는다. 체감 기반 비교와 공식 문서에서 확인한 사실 위주로 쓴다.

"MySQL이 빠르다"는 말의 실체

단순 CRUD에서는 맞다

새 프로젝트에서 사용자 활동 로그를 저장하는 API를 만들다가 둘을 비교할 기회가 생겼다. 같은 스키마, 같은 인덱스 구조에서 단순 INSERTSELECT ... WHERE id = ? 쿼리를 돌렸을 때, MySQL이 체감상 10~15% 빨랐다. 이건 예상한 결과다.

MySQL의 InnoDB 엔진은 단순 키-값 조회에 최적화된 구조를 갖고 있다. 클러스터드 인덱스가 기본이라 PK 기반 조회가 빠르고, 커넥션 처리도 가볍다. 읽기 비중이 80% 이상이고 조인이 2~3개 테이블 수준이라면 MySQL이 더 나은 선택일 수 있다.

쿼리가 복잡해지면 역전된다

문제는 쿼리가 복잡해질 때다. 서브쿼리 3단 이상, CTE(Common Table Expression) 활용, 윈도우 함수 조합 같은 상황에서 PostgreSQL의 쿼리 플래너가 확실히 우수하다. MySQL 8.0에서 CTE와 윈도우 함수가 추가됐지만, PostgreSQL은 이걸 8.4 시절(2008년)부터 지원해왔다. 최적화 깊이가 다르다.

실제로 로그 테이블에서 사용자별 최근 7일 활동을 집계하는 쿼리를 짰는데, PostgreSQL에서는 LATERAL JOIN + 윈도우 함수 조합이 깔끔하게 동작했다. 같은 로직을 MySQL에서 구현하려니 임시 테이블을 거쳐야 했고, 실행 시간도 체감상 2배 가까이 걸렸다.

"MySQL이 빠르다"는 말은 조건부다. 단순 CRUD면 맞고, 분석성 쿼리가 섞이면 틀리다.

JSON 처리 — 여기서 차이가 확 벌어진다

PostgreSQL의 JSONB는 진짜 네이티브다

PostgreSQL의 jsonb 타입은 바이너리 형태로 저장된다. GIN 인덱스를 걸면 JSON 내부 키에 대한 검색이 인덱스 스캔으로 처리된다. 이게 핵심이다.

-- PostgreSQL: JSONB 컬럼에 GIN 인덱스 생성
CREATE INDEX idx_metadata ON user_logs USING GIN (metadata jsonb_path_ops);

-- 특정 키 값으로 검색 — 인덱스 타고 빠르게 조회된다
SELECT * FROM user_logs
WHERE metadata @> '{"action": "purchase", "platform": "mobile"}';

@> 연산자(포함 연산)가 GIN 인덱스를 타기 때문에, JSON 안에 어떤 키-값 조합이 들어있든 빠르게 찾을 수 있다. 100만 건 기준으로 테스트했을 때, 인덱스 없이 2초 넘게 걸리던 쿼리가 인덱스 추가 후 20ms 이내로 떨어졌다.

MySQL의 JSON은 반쪽이다

MySQL도 JSON 타입을 지원한다. MySQL 8.0에서 도입됐고, 8.4에서 JSON_TABLE() 함수가 개선되면서 꽤 쓸만해졌다. 그런데 근본적인 한계가 있다.

-- MySQL: JSON 컬럼 검색 — 가상 컬럼 + 인덱스가 필요하다
ALTER TABLE user_logs
ADD COLUMN action VARCHAR(50)
  GENERATED ALWAYS AS (JSON_UNQUOTE(JSON_EXTRACT(metadata, '$.action'))) VIRTUAL;

CREATE INDEX idx_action ON user_logs (action);

MySQL에서 JSON 내부 값을 인덱싱하려면 가상 컬럼(generated column)을 만들고, 그 컬럼에 인덱스를 따로 걸어야 한다. 검색하고 싶은 키가 3개면 가상 컬럼도 3개 만들어야 한다. JSON의 유연함을 쓰려고 JSON을 도입했는데, 결국 스키마를 확장하는 꼴이 된다.

JSON 데이터를 “가끔 저장하고 거의 조회 안 하는” 용도라면 MySQL의 JSON으로 충분하다. 그런데 JSON 필드를 WHERE 조건에 자주 쓰거나, 키 구조가 유동적이라면 PostgreSQL의 JSONB가 압도적으로 유리하다.

프로젝트 초기에 "JSON은 보조 데이터니까 MySQL로 충분하겠지"라고 판단했다가 3개월 뒤에 JSON 기반 필터링 요구사항이 쏟아져서 결국 PostgreSQL로 마이그레이션한 경험이 있다. 가상 컬럼을 6개까지 추가하다가 "이건 아니다" 싶었다. 마이그레이션 자체는 pgloader로 하루 만에 끝났지만, ORM 쿼리를 전부 고치는 데 일주일이 걸렸다.

확장성과 고급 기능 비교

기능 PostgreSQL 17 MySQL 8.4 LTS
파티셔닝 선언적 파티셔닝, 리스트/레인지/해시 레인지/리스트/해시/키
병렬 쿼리 병렬 시퀀셜 스캔, 병렬 조인 지원 제한적 (COUNT 등 일부만)
논리 복제 내장, 세밀한 퍼블리케이션 제어 내장, GTID 기반
확장 시스템 CREATE EXTENSION으로 수백 개 확장 설치 플러그인 방식, 제한적
Full-Text Search 내장 (ts_vector, ts_query) 내장 (FULLTEXT 인덱스)
벡터 검색 pgvector 확장 없음 (서드파티 의존)
UPSERT ON CONFLICT DO UPDATE ON DUPLICATE KEY UPDATE
스토리지 엔진 단일 (heap) 선택 가능 (InnoDB, MyISAM 등)

테이블로 보면 기능 수 자체는 PostgreSQL이 우위에 있다. 그런데 이걸 "PostgreSQL이 무조건 좋다"로 해석하면 안 된다. MySQL의 장점은 단순함이다. 스토리지 엔진 선택지가 있다는 건 곧 복잡도가 낮다는 뜻이 아니라 유연하다는 뜻이기도 하다.

PostgreSQL 확장 생태계의 진짜 가치

PostgreSQL에서 CREATE EXTENSION은 그냥 패키지 설치가 아니다. DB 엔진 레벨에서 새로운 데이터 타입, 인덱스 방식, 함수를 추가할 수 있다는 뜻이다.

대표적인 예가 pgvector다. AI 서비스에서 임베딩 벡터를 저장하고 유사도 검색을 해야 할 때, 별도의 벡터 DB(Pinecone, Milvus 등)를 띄우지 않고 PostgreSQL 안에서 해결할 수 있다.

-- pgvector 설치 및 벡터 컬럼 생성
CREATE EXTENSION vector;

CREATE TABLE documents (
  id SERIAL PRIMARY KEY,
  content TEXT,
  embedding vector(1536)  -- OpenAI text-embedding-3-small 차원
);

-- IVFFlat 인덱스로 근사 최근접 이웃 검색
CREATE INDEX idx_embedding ON documents
USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);

pgvector 0.7.0(2024년 출시)부터 HNSW 인덱스도 지원한다. IVFFlat보다 검색 정확도가 높고, 빌드 시간도 개선됐다. (출처: pgvector GitHub v0.7.0 Release Notes)

MySQL에서 같은 걸 하려면 별도의 벡터 DB를 운영해야 한다. 인프라가 하나 더 늘어난다는 건, 운영 비용과 장애 포인트가 늘어난다는 뜻이다.

파티셔닝 — 둘 다 되지만 깊이가 다르다

MySQL의 파티셔닝도 나쁘지 않다. 레인지 파티셔닝으로 로그 테이블을 월별로 나누는 정도는 MySQL에서도 잘 동작한다. 그런데 PostgreSQL 17에서 추가된 파티션 프루닝 최적화는 인상적이다. 조인 시 불필요한 파티션을 더 공격적으로 제외하기 때문에, 대용량 테이블의 조인 성능이 이전 버전 대비 개선됐다. (출처: PostgreSQL 17 Release Notes)

VACUUM — PostgreSQL의 유일한 약점이라는 통념

"PostgreSQL은 VACUUM 때문에 운영이 힘들다"는 말을 자주 듣는다. 이것도 반만 맞는 얘기다.

PostgreSQL은 MVCC(Multi-Version Concurrency Control) 구현 방식 때문에, 업데이트나 삭제된 행의 이전 버전이 테이블에 남는다. 이걸 정리하는 게 VACUUM이다. MySQL의 InnoDB도 MVCC를 쓰지만, undo 로그를 별도 공간에서 관리하기 때문에 테이블 자체가 부풀지 않는다.

autovacuum 설정을 기본값으로 놔두면 문제가 생길 수 있다. 트래픽이 높은 테이블에서 autovacuum이 뒤처지면 테이블 bloat가 심해지고, 결국 쿼리 성능이 떨어진다. 이건 사실이다.

그런데 autovacuum_vacuum_scale_factor를 0.01로 낮추고 autovacuum_naptime을 15초 정도로 조정하면 대부분 해결된다. 이건 어려운 튜닝이 아니라 설정값 두 개 바꾸는 일이다.

-- 특정 테이블에 대해 공격적인 autovacuum 설정
ALTER TABLE user_logs SET (
  autovacuum_vacuum_scale_factor = 0.01,  -- 1% 변경 시 vacuum 실행
  autovacuum_analyze_scale_factor = 0.005,
  autovacuum_vacuum_cost_delay = 2        -- vacuum 속도 높임 (기본 2ms)
);

AWS RDS나 Cloud SQL 같은 매니지드 서비스를 쓰면 autovacuum이 이미 튜닝되어 있는 경우가 많다. 직접 서버를 운영하는 게 아니라면 VACUUM이 "약점"이라고 부르기 어렵다.

ORM과의 궁합

Django, Rails, Spring 같은 주요 프레임워크는 둘 다 잘 지원한다. 이 부분은 간단하다. 어떤 ORM을 쓰든 PostgreSQL과 MySQL 둘 다 문제없이 동작한다.

차이가 나는 건 ORM의 고급 기능을 쓸 때다. Django ORM의 JSONField는 PostgreSQL 백엔드에서만 GIN 인덱스 기반 __contains 룩업을 지원한다. MySQL 백엔드에서는 JSONField가 동작하긴 하지만 인덱스를 활용한 효율적 검색이 안 된다. SQLAlchemy도 비슷한 상황이다.

팀에서 TypeScript를 쓰고 있다면 Prisma 기준으로 보자. Prisma는 PostgreSQL의 배열 타입(TEXT[]), 열거형(ENUM), JSONB 필터링을 네이티브로 지원한다. MySQL 커넥터에서는 이런 기능이 제한적이다. (출처: Prisma 공식 문서, 2026-04 기준)

pgvector와 AI 인프라 — 이게 2026년의 결정적 차이다

2024~2026년 사이에 가장 큰 변화는 AI 기능이 백엔드 서비스에 기본으로 들어가기 시작했다는 거다. 검색 API에 시멘틱 검색을 붙이거나, 추천 시스템에 임베딩 유사도를 쓰는 게 일상이 됐다.

Pinecone 따로 띄울 것인가, PostgreSQL에서 해결할 것인가

벡터 검색 전용 DB(Pinecone, Weaviate, Qdrant)는 성능이 좋다. 수억 건의 벡터를 다루는 대규모 서비스라면 전용 DB가 맞다. 그런데 100만 건 이하의 벡터를 다루는 중소 규모 서비스에서 별도 인프라를 띄우는 건 과하다.

pgvector를 쓰면 기존 PostgreSQL 안에서 벡터 저장, 인덱싱, 유사도 검색이 가능하다. 트랜잭션도 일반 테이블과 동일하게 적용된다. 사용자 프로필 테이블에 임베딩 컬럼을 추가하고, 일반 SQL 조인으로 "이 사용자와 유사한 사용자"를 뽑을 수 있다는 뜻이다.

MySQL에서 이걸 하려면 외부 벡터 DB를 붙이고, 애플리케이션 레벨에서 두 DB의 결과를 조합해야 한다. 코드 복잡도가 올라가고, 데이터 정합성 관리도 까다로워진다.

HNSW vs IVFFlat — 인덱스 선택

pgvector에서 벡터 인덱스는 두 가지를 고를 수 있다. IVFFlat은 빌드가 빠르고 메모리를 적게 쓰지만 정확도가 상대적으로 낮다. HNSW는 빌드가 느리고 메모리를 더 쓰지만 검색 정확도(recall)가 높다.

100만 건 이하, recall 95% 이상이 필요한 서비스라면 HNSW를 쓰는 게 낫다. 빌드 시간은 100만 건 기준 체감상 수 분 정도 차이가 나는데, 인덱스를 매일 리빌드하는 게 아니라면 큰 문제가 안 된다.

운영 관점 — 현실적으로 뭐가 더 편한가

PostgreSQL이 기능이 많다고 운영도 편한 건 아니다.

MySQL은 설정이 단순하다. my.cnf에서 innodb_buffer_pool_sizemax_connections 정도만 잡아주면 웬만한 워크로드를 소화한다. 모니터링도 SHOW STATUS, SHOW PROCESSLIST로 대부분 해결되고, 커뮤니티 자료가 넘쳐난다. 3년차 백엔드 개발자가 혼자 운영하기에는 MySQL이 확실히 진입장벽이 낮다.

PostgreSQL은 postgresql.conf 설정 항목이 300개가 넘는다. shared_buffers, work_mem, effective_cache_size, maintenance_work_mem 같은 메모리 관련 설정만 4~5개다. 잘못 건드리면 오히려 성능이 떨어지는 경우도 있다. work_mem을 너무 크게 잡아서 동시 쿼리가 많을 때 OOM이 난 적도 있다. 이건 공식 문서에서도 "커넥션 수 × work_mem"을 전체 메모리와 비교하라고 경고하는 부분이다.

매니지드 서비스(AWS RDS, Google Cloud SQL, Supabase)를 쓴다면 이 차이가 많이 줄어든다. 특히 Supabase는 PostgreSQL + pgvector + Auth + Realtime을 한 번에 제공하기 때문에, PostgreSQL의 운영 부담을 거의 느끼지 않고 쓸 수 있다.

언제 뭘 고를지 — 프로젝트 유형별 판단

"무조건 PostgreSQL" 또는 "무조건 MySQL"이라는 답은 없다. 프로젝트 특성에 따라 달라진다.

MySQL이 나은 경우: 읽기 비중이 높은 단순 CRUD 서비스, WordPress 같은 기존 생태계 활용, 팀에 MySQL 경험자가 많고 DBA가 없는 상황. 이런 조건에서 굳이 PostgreSQL을 도입하면 러닝커브만 높아진다.

PostgreSQL이 나은 경우: JSON 기반 유연한 스키마가 필요한 서비스, 분석성 쿼리가 섞인 OLTP, 벡터 검색이나 Full-Text Search를 DB 레벨에서 처리하고 싶은 경우, 그리고 PostGIS로 지리 데이터를 다루는 경우. 이런 요구사항이 하나라도 있으면 PostgreSQL이 장기적으로 유리하다.

2026년 기준으로 새 프로젝트를 시작한다면, AI 기능을 나중에 붙일 가능성이 조금이라도 있으면 PostgreSQL을 선택하는 게 안전하다. MySQL에서 PostgreSQL로 마이그레이션하는 건 가능하지만, 프로젝트 중반에 DB를 바꾸는 비용은 초기 선택 비용의 5~10배다. 이건 아무리 pgloader 같은 도구가 좋아져도 ORM 쿼리, 마이그레이션 스크립트, CI/CD 파이프라인 전부 손대야 하기 때문이다. 현재 운영 중인 프로젝트에 pgvector를 추가해서 벡터 검색 API를 붙이는 작업은 CREATE EXTENSION vector; 한 줄이면 시작할 수 있다.

관련 글

Chiko IT
Chiko IT

Platform Engineer. Python, AI, Infra에 관심이 많습니다.