VillageSQL is a drop-in replacement for MySQL with extensions.
All examples on this page work on VillageSQL. Install Now →
어떤 접근 방식을 사용해야 하나요
- 일회성 전환 —
pg_dump와LOAD DATA INFILE로 덤프/복원합니다. 아래의 마이그레이션 도구와 마이그레이션 접근 방식을 보세요. - 마이그레이션 중에 소스 데이터베이스가 계속 살아서 쓰기 가능해야 하거나, 지속적인 동기화가 필요한 경우 — 일회성 덤프 대신 JDBC 싱크 커넥터와 함께 Debezium을 구성해 지속적으로 복제하세요.
마이그레이션 도구
pg_dump + 수동 스키마 변환이 주된 경로이며, 표준 PostgreSQL과 MySQL 클라이언트 도구 외에는 아무것도 필요하지 않습니다:
pg_dump의 스키마 출력은 PostgreSQL 구문(SERIAL, BOOLEAN, PostgreSQL 고유 CREATE TABLE 절)을 사용하므로, 스키마 파일은 MySQL에서 실행되기 전에 아래의 타입 매핑 표를 사용해 수동으로 변환해야 합니다. 모든 경우에 이 변환을 자동으로 그리고 올바르게 수행하는 도구는 없습니다 — 대부분의 마이그레이션에서 이것을 기본 경로로 생각하세요.
JDBC 싱크 커넥터와 함께 쓰는 Debezium(debezium.io, Apache-2.0 라이선스, 활발히 유지 관리됨)은 PostgreSQL의 미리 쓰기 로그를 읽어 Kafka Connect를 통해 변경을 MySQL로 스트리밍하며, 일회성 복사 대신 지속적인 복제를 제공합니다. 마이그레이션 중에 소스 데이터베이스가 계속 살아서 쓰기 가능해야 하거나, 전환이 아니라 지속적인 동기화가 필요할 때 올바른 도구입니다. 실행 중인 Kafka와 Kafka Connect 클러스터가 필요한데, 이는 일회성 마이그레이션에 필요한 것보다 많은 인프라입니다 — 한 번의 전환이라면 대신 pg_dump를 사용하세요.
데이터 타입 매핑
시퀀스 대 AUTO_INCREMENT
PostgreSQL은 자동 증가 값에 시퀀스(독립적인 객체)를 사용합니다. MySQL은AUTO_INCREMENT를 열 속성으로 사용합니다:
PostgreSQL:
SELECT LAST_INSERT_ID();(PostgreSQL의 currval() 또는 RETURNING id에 해당).
SQL 방언 차이
문자열 연결:ANSI_QUOTES SQL 모드가 활성화된 경우에만 식별자에 큰따옴표를 받아들입니다.
ILIKE(대소문자 구분 없는 LIKE):
utf8mb4_0900_ai_ci(MySQL 8.x 기본값) 또는 비슷한 _ci 콜레이션에서 기본적으로 대소문자를 구분하지 않습니다. 대소문자를 구분하는 매칭이 필요하면 _bin 콜레이션을 사용하세요.
RETURNING 절:
mydb.public.orders). MySQL은 “스키마”와 “데이터베이스”를 같은 뜻으로 사용합니다 — 데이터베이스 안의 스키마라는 개념이 없습니다. PostgreSQL이 스키마라고 부르는 것을 MySQL은 데이터베이스라고 부릅니다.
UNIQUE 인덱스의 NULL 처리:
PostgreSQL은 고유 열에 여러 NULL 값을 허용합니다(NULL은 서로 같지 않습니다). MySQL(InnoDB)도 고유 인덱스에 여러 NULL을 허용합니다 — 동작이 같습니다.
CTE:
MySQL은 재귀 CTE를 포함한 CTE를 지원합니다. MySQL 공통 테이블 표현식을 보세요.
윈도 함수:
MySQL은 윈도 함수를 지원합니다. MySQL의 윈도 함수를 보세요.
저장 프로시저와 함수:
PostgreSQL은 예외 처리와 풍부한 제어 흐름을 갖춘 PL/pgSQL로 절차적 로직을 작성합니다. MySQL은 자체 절차적 SQL 방언을 사용하고, PL/pgSQL을 지원하지 않으며, 프로시저 본문 안의 세미콜론이 문장의 끝으로 해석되지 않도록 클라이언트 도구에서 DELIMITER 변경이 필요합니다:
DECLARE ... HANDLER가 다르게 동작하므로 예외 처리 로직(EXCEPTION WHEN ... 블록)에 가장 많은 재작업이 필요할 것으로 예상하세요. MySQL의 저장 프로시저를 보세요.
식별자의 대소문자 구분:
PostgreSQL은 인용되지 않은 식별자를 소문자로 변환하고 인용된 식별자("MyTable")는 대소문자를 구분해 다룹니다. MySQL의 규칙은 다릅니다: 데이터베이스와 테이블 이름이 대소문자를 구분하는지는 인용이 아니라 lower_case_table_names 시스템 변수와 기반 파일 시스템에 따라 결정됩니다. 열, 인덱스, 저장 루틴 이름은 플랫폼과 관계없이 조회 목적으로는 절대 대소문자를 구분하지 않습니다. 대소문자를 구분하는 PostgreSQL 테이블 이름에 의존하던 스키마가 마이그레이션 후에도 똑같이 동작한다고 가정하지 마세요 — 대상 서버에서 lower_case_table_names를 확인하세요.
MySQL 등가물이 없는 기능
일부 PostgreSQL 기능에는 직접적인 등가물이 없습니다:확장 가져오기
PostgreSQL은 네이티브로 제공하지 않는 기능을 위해pgcrypto, pg_trgm, uuid-ossp 같은 확장에 의존합니다. 순정 MySQL에는 이런 기능을 추가하는 등가 메커니즘이 없지만 — VillageSQL에는 있습니다: VillageSQL 확장 프레임워크(VEF)를 사용하면 INSTALL EXTENSION으로 사용자 정의 SQL 함수와 타입을 서버에 설치할 수 있습니다. VillageSQL의 확장 중 일부는 위의 PostgreSQL 확장을 직접 이식한 것입니다:
PostgreSQL 데이터베이스가 여기 없는 확장에 의존한다면 아직 VillageSQL 등가물이 없을 수 있습니다 — 그 기능이 영영 사라졌다고 가정하기 전에 VillageSQL 문서의 확장 카탈로그를 확인하세요. 그리고 그 의존성이 여러분의 팀이 직접 작성한 사용자 정의 PostgreSQL 확장이라면, VEF는 그것을 이식하는 도구이기도 합니다:
vsql-extension-builder 스킬은 AI 코딩 에이전트가 요구 사항부터 테스트되고 문서화된 .veb 패키지까지, VillageSQL에서 사용자 정의 확장을 다시 만드는 과정을 안내합니다.
마이그레이션 접근 방식
- PostgreSQL에서 스키마를 내보내고(
pg_dump --schema-only) 위의 타입 매핑을 사용해 각 테이블을 수동으로 변환합니다. - PostgreSQL에서 데이터를 CSV로 내보냅니다(
COPY table TO '/tmp/table.csv' CSV HEADER). LOAD DATA INFILE또는mysqlimport로 MySQL에 데이터를 로드합니다.- 쿼리를 테스트합니다 — 모든 PostgreSQL 고유 구문을 찾아 다시 작성합니다.
- 개수와 체크섬을 검증합니다 — 모든 테이블에
SELECT COUNT(*); 주요 행을 표본 검사합니다.
자주 묻는 질문
MySQL은 PostgreSQL의 ON CONFLICT 같은 UPSERT를 지원하나요?
예, 다른 구문으로 지원합니다. MySQL의 UPSERT를 보세요. MySQL의 INSERT ... ON DUPLICATE KEY UPDATE와 REPLACE INTO는 PostgreSQL의 ON CONFLICT DO UPDATE와 ON CONFLICT DO NOTHING과 같은 사용 사례를 다룹니다.
MySQL에서 PostgreSQL의 BOOLEAN 열을 어떻게 처리하나요?
TINYINT(1)을 사용하세요. 참에는 1, 거짓에는 0을 저장합니다. 대부분의 MySQL 클라이언트 라이브러리와 ORM은 이것을 자동으로 처리하고 TINYINT(1) 열을 불리언으로 보여줍니다. BIT(1)도 사용할 수 있지만 TINYINT(1)이 더 폭넓은 도구 지원을 받습니다.
문제 해결
함께 보기
- MySQL의 스키마 마이그레이션 — 마이그레이션에 필요한 DDL 변경 실행하기
- MySQL 데이터 타입 선택하기 — PostgreSQL 타입을 MySQL 등가물에 매핑하기
- MySQL 대 PostgreSQL: 무엇을 사용해야 하나요? — 마이그레이션 뒤에 있는 기능별 비교