> ## Documentation Index
> Fetch the complete documentation index at: https://villagesql.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# PostgreSQL을 MySQL로 변환하기: 마이그레이션 가이드

> PostgreSQL 데이터베이스를 MySQL로 마이그레이션하는 방법: 데이터 타입 매핑, AUTO_INCREMENT 대 시퀀스, SQL 방언 차이, 피해야 할 마이그레이션 함정입니다.

> [card] [VillageSQL is a drop-in replacement for MySQL with extensions.](https://villagesql.com/docs/en/8.4/stable/install-server.md)
>
> All examples on this page work on VillageSQL. Install Now →

PostgreSQL과 MySQL은 ANSI SQL 구문을 공유하지만, 마이그레이션에 데이터 타입, 기본 동작, SQL 방언 차이에 대한 세심한 주의가 필요할 만큼 갈라져 있습니다. 작업의 대부분은 스키마 변환과 PostgreSQL 고유 기능에 의존하는 쿼리 수정입니다.

이 가이드는 PostgreSQL을 MySQL로 마이그레이션하는 것을 다룹니다. [VillageSQL](https://villagesql.com/install) — PostgreSQL 스타일의 확장 프레임워크를 위에 더한, MySQL의 오픈 소스 드롭인 대체품 — 같은 MySQL 호환 서버로 옮길 때 흔한 방향입니다. 그 프레임워크 덕분에 이 가이드에는 스키마와 데이터뿐 아니라 PostgreSQL 확장이 자리 잡을 곳도 있습니다; 아래의 [확장 가져오기](#bringing-your-extensions)를 보세요. 데이터를 반대 방향으로 옮긴다면, 아래의 타입 매핑과 방언 차이는 역으로 여전히 적용되지만 마이그레이션 도구 섹션은 적용되지 않습니다.

## 어떤 접근 방식을 사용해야 하나요

* **일회성 전환** — `pg_dump`와 `LOAD DATA INFILE`로 덤프/복원합니다. 아래의 [마이그레이션 도구](#migration-tools)와 [마이그레이션 접근 방식](#migration-approach)을 보세요.
* **마이그레이션 중에 소스 데이터베이스가 계속 살아서 쓰기 가능해야 하거나, 지속적인 동기화가 필요한 경우** — 일회성 덤프 대신 JDBC 싱크 커넥터와 함께 Debezium을 구성해 지속적으로 복제하세요.

## 마이그레이션 도구

**`pg_dump` + 수동 스키마 변환**이 주된 경로이며, 표준 PostgreSQL과 MySQL 클라이언트 도구 외에는 아무것도 필요하지 않습니다:

```bash theme={null}
# Schema only, plain SQL text
pg_dump -s -f schema.sql mydb

# Data only, as INSERT statements (portable, but slower to load than COPY)
pg_dump -a --inserts -f data.sql mydb
```

`pg_dump`의 스키마 출력은 PostgreSQL 구문(`SERIAL`, `BOOLEAN`, PostgreSQL 고유 `CREATE TABLE` 절)을 사용하므로, 스키마 파일은 MySQL에서 실행되기 전에 아래의 타입 매핑 표를 사용해 수동으로 변환해야 합니다. 모든 경우에 이 변환을 자동으로 그리고 올바르게 수행하는 도구는 없습니다 — 대부분의 마이그레이션에서 이것을 기본 경로로 생각하세요.

**JDBC 싱크 커넥터와 함께 쓰는 Debezium**([debezium.io](https://debezium.io), Apache-2.0 라이선스, 활발히 유지 관리됨)은 PostgreSQL의 미리 쓰기 로그를 읽어 Kafka Connect를 통해 변경을 MySQL로 스트리밍하며, 일회성 복사 대신 지속적인 복제를 제공합니다. 마이그레이션 중에 소스 데이터베이스가 계속 살아서 쓰기 가능해야 하거나, 전환이 아니라 지속적인 동기화가 필요할 때 올바른 도구입니다. 실행 중인 Kafka와 Kafka Connect 클러스터가 필요한데, 이는 일회성 마이그레이션에 필요한 것보다 많은 인프라입니다 — 한 번의 전환이라면 대신 `pg_dump`를 사용하세요.

## 데이터 타입 매핑

| PostgreSQL 타입 | MySQL 등가물 | 참고 |
| :- | :- | :- |
| `SERIAL` / `BIGSERIAL` | `INT AUTO_INCREMENT` / `BIGINT AUTO_INCREMENT` | MySQL은 시퀀스가 아니라 AUTO\_INCREMENT를 사용 |
| `TEXT` | `TEXT` 또는 `LONGTEXT` | MySQL TEXT는 65,535바이트로 제한; 더 큰 값에는 LONGTEXT 사용 |
| `VARCHAR`(길이 제한 없음) | 지원 안 됨 | MySQL VARCHAR는 길이가 필요; 대신 TEXT 사용 |
| `BOOLEAN` | `TINYINT(1)` | MySQL에는 진정한 불리언이 없음; 0/1 관례 |
| `BYTEA` | `BLOB` 또는 `LONGBLOB` | 바이너리 데이터 |
| `JSONB` | `JSON` | MySQL JSON은 검증과 함께 텍스트로 저장; JSONB 바이너리 형식 없음 |
| `UUID` | `CHAR(36)` 또는 `BINARY(16)` | 네이티브 UUID 타입 없음; [MySQL의 UUID](https://villagesql.com/docs/ko/guides/uuids.md) 참조 |
| `TIMESTAMP WITH TIME ZONE` | `DATETIME` + 명시적 시간대 처리 | MySQL DATETIME에는 시간대가 없음; [타임스탬프와 시간대](https://villagesql.com/docs/ko/guides/timestamps-timezones.md) 참조 |
| `NUMERIC` / `DECIMAL` | `DECIMAL(p,s)` | MySQL에서는 정밀도와 스케일이 필요 |
| `ARRAY` | 지원 안 됨 | 자식 테이블로 정규화 |
| `HSTORE` | `JSON` | 키-값 쌍; MySQL에서는 JSON 사용 |
| `INTERVAL` | 열 타입으로 지원 안 됨 | INT 초로 저장하거나 애플리케이션 측 로직 사용 |

## 시퀀스 대 AUTO\_INCREMENT

PostgreSQL은 자동 증가 값에 시퀀스(독립적인 객체)를 사용합니다. MySQL은 `AUTO_INCREMENT`를 열 속성으로 사용합니다:

**PostgreSQL:**

```sql theme={null}
CREATE SEQUENCE orders_id_seq;
CREATE TABLE orders (
    id INTEGER DEFAULT nextval('orders_id_seq') PRIMARY KEY
);
```

**MySQL 등가물:**

```sql theme={null}
CREATE TABLE orders (
    id INT AUTO_INCREMENT PRIMARY KEY
);
```

MySQL에서 마지막으로 삽입된 ID를 얻으려면: `SELECT LAST_INSERT_ID();`(PostgreSQL의 `currval()` 또는 `RETURNING id`에 해당).

## SQL 방언 차이

**문자열 연결:**

```sql theme={null}
-- PostgreSQL
SELECT first_name || ' ' || last_name FROM users;

-- MySQL
SELECT CONCAT(first_name, ' ', last_name) FROM users;
```

**문자열 인용:**

```sql theme={null}
-- PostgreSQL: double quotes for identifiers, single quotes for strings
SELECT "first_name" FROM users WHERE status = 'active';

-- MySQL: backticks for identifiers, single quotes for strings
SELECT `first_name` FROM users WHERE status = 'active';
```

MySQL은 `ANSI_QUOTES` SQL 모드가 활성화된 경우에만 식별자에 큰따옴표를 받아들입니다.

**ILIKE(대소문자 구분 없는 LIKE):**

```sql theme={null}
-- PostgreSQL
SELECT * FROM users WHERE name ILIKE '%alice%';

-- MySQL (case-insensitive by default with ci collations)
SELECT * FROM users WHERE name LIKE '%alice%';
```

MySQL 문자열 비교는 `utf8mb4_0900_ai_ci`(MySQL 8.x 기본값) 또는 비슷한 `_ci` 콜레이션에서 기본적으로 대소문자를 구분하지 않습니다. 대소문자를 구분하는 매칭이 필요하면 `_bin` 콜레이션을 사용하세요.

**RETURNING 절:**

```sql theme={null}
-- PostgreSQL
INSERT INTO users (name) VALUES ('Alice') RETURNING id;

-- MySQL (no RETURNING)
INSERT INTO users (name) VALUES ('Alice');
SELECT LAST_INSERT_ID();
```

**LIMIT / OFFSET:**

```sql theme={null}
-- PostgreSQL
SELECT * FROM orders LIMIT 10 OFFSET 20;

-- MySQL (same syntax)
SELECT * FROM orders LIMIT 10 OFFSET 20;
-- or shorthand:
SELECT * FROM orders LIMIT 20, 10;  -- LIMIT offset, count
```

**스키마 대 데이터베이스:**
PostgreSQL은 데이터베이스 안에 스키마를 사용합니다(`mydb.public.orders`). MySQL은 "스키마"와 "데이터베이스"를 같은 뜻으로 사용합니다 — 데이터베이스 안의 스키마라는 개념이 없습니다. PostgreSQL이 스키마라고 부르는 것을 MySQL은 데이터베이스라고 부릅니다.

**UNIQUE 인덱스의 NULL 처리:**
PostgreSQL은 고유 열에 여러 NULL 값을 허용합니다(NULL은 서로 같지 않습니다). MySQL(InnoDB)도 고유 인덱스에 여러 NULL을 허용합니다 — 동작이 같습니다.

**CTE:**
MySQL은 재귀 CTE를 포함한 CTE를 지원합니다. [MySQL 공통 테이블 표현식](https://villagesql.com/docs/ko/tutorial/cte.md)을 보세요.

**윈도 함수:**
MySQL은 윈도 함수를 지원합니다. [MySQL의 윈도 함수](https://villagesql.com/docs/ko/guides/window-functions.md)를 보세요.

**저장 프로시저와 함수:**
PostgreSQL은 예외 처리와 풍부한 제어 흐름을 갖춘 PL/pgSQL로 절차적 로직을 작성합니다. MySQL은 자체 절차적 SQL 방언을 사용하고, PL/pgSQL을 지원하지 않으며, 프로시저 본문 안의 세미콜론이 문장의 끝으로 해석되지 않도록 클라이언트 도구에서 `DELIMITER` 변경이 필요합니다:

```sql theme={null}
-- MySQL: a stored procedure with an OUT parameter
DELIMITER ;;
CREATE PROCEDURE total_orders(IN cust_id INT, OUT total INT)
BEGIN
    SELECT COUNT(*) INTO total FROM orders WHERE customer_id = cust_id;
END;;
DELIMITER ;

CALL total_orders(1, @t);
SELECT @t;
```

PL/pgSQL에서 MySQL의 절차적 구문으로의 자동 변환은 없습니다 — 각 프로시저를 손으로 다시 작성하고, MySQL의 `DECLARE ... HANDLER`가 다르게 동작하므로 예외 처리 로직(`EXCEPTION WHEN ...` 블록)에 가장 많은 재작업이 필요할 것으로 예상하세요. [MySQL의 저장 프로시저](https://villagesql.com/docs/ko/guides/stored-procedures.md)를 보세요.

**식별자의 대소문자 구분:**
PostgreSQL은 인용되지 않은 식별자를 소문자로 변환하고 인용된 식별자(`"MyTable"`)는 대소문자를 구분해 다룹니다. MySQL의 규칙은 다릅니다: 데이터베이스와 테이블 이름이 대소문자를 구분하는지는 인용이 아니라 `lower_case_table_names` 시스템 변수와 기반 파일 시스템에 따라 결정됩니다. 열, 인덱스, 저장 루틴 이름은 플랫폼과 관계없이 조회 목적으로는 절대 대소문자를 구분하지 않습니다. 대소문자를 구분하는 PostgreSQL 테이블 이름에 의존하던 스키마가 마이그레이션 후에도 똑같이 동작한다고 가정하지 마세요 — 대상 서버에서 `lower_case_table_names`를 확인하세요.

## MySQL 등가물이 없는 기능

일부 PostgreSQL 기능에는 직접적인 등가물이 없습니다:

| PostgreSQL 기능 | MySQL 상황 |
| :- | :- |
| 열 타입으로서의 배열 | 자식 테이블로 정규화 |
| 테이블 상속 | 지원 안 됨; 별도 테이블 사용 |
| 사용자 정의 연산자 | 지원 안 됨 |
| 완전한 `LISTEN`/`NOTIFY` | 지원 안 됨; 애플리케이션 측 폴링 또는 메시지 큐 사용 |
| 부분 인덱스(인덱스의 `WHERE` 절) | 지원 안 됨; 생성 열을 사용해 부분 집합을 인덱싱 |
| 구체화 뷰 | 네이티브로 지원 안 됨; 테이블 + 저장 프로시저로 근사 |
| 대량 로드를 위한 `COPY` 명령 | `LOAD DATA INFILE` 또는 MySQL Shell의 `loadDump` 사용 |

## 확장 가져오기

PostgreSQL은 네이티브로 제공하지 않는 기능을 위해 `pgcrypto`, `pg_trgm`, `uuid-ossp` 같은 확장에 의존합니다. 순정 MySQL에는 이런 기능을 추가하는 등가 메커니즘이 없지만 — VillageSQL에는 있습니다: VillageSQL 확장 프레임워크(VEF)를 사용하면 `INSTALL EXTENSION`으로 사용자 정의 SQL 함수와 타입을 서버에 설치할 수 있습니다. VillageSQL의 확장 중 일부는 위의 PostgreSQL 확장을 직접 이식한 것입니다:

| PostgreSQL 확장 | VillageSQL 확장 | 다루는 범위 |
| :- | :- | :- |
| `pgcrypto` | `vsql_crypto` | 해싱, HMAC, AES 암호화, 비밀번호 해싱(PBKDF2만 — bcrypt 해시는 그대로 옮겨지지 않음). [MySQL의 데이터 해싱](https://villagesql.com/docs/ko/guides/hashing-data.md) 참조. |
| `uuid-ossp` | `vsql_uuid` | 네이티브 UUID 열 타입과 v1–v7 생성 함수. [MySQL의 UUID](https://villagesql.com/docs/ko/guides/uuids.md) 참조. |
| —(내장 `inet`/`cidr`/`macaddr` 타입) | `vsql_network_address` | IPv4, IPv6, MAC 주소 타입. [MySQL에 IP 주소 저장하기](https://villagesql.com/docs/ko/guides/storing-ip-addresses.md) 참조. |
| `pg_trgm` | `vsql_trgm` | 트라이그램 기반 문자열 유사도 검색. [MySQL의 트라이그램 유사도](https://villagesql.com/docs/ko/guides/trigram-similarity.md) 참조. |

PostgreSQL 데이터베이스가 여기 없는 확장에 의존한다면 아직 VillageSQL 등가물이 없을 수 있습니다 — 그 기능이 영영 사라졌다고 가정하기 전에 VillageSQL 문서의 확장 카탈로그를 확인하세요. 그리고 그 의존성이 여러분의 팀이 직접 작성한 사용자 정의 PostgreSQL 확장이라면, VEF는 그것을 이식하는 도구이기도 합니다: [`vsql-extension-builder`](https://github.com/villagesql/villagesql-skills/tree/main/skills/vsql-extension-builder) 스킬은 AI 코딩 에이전트가 요구 사항부터 테스트되고 문서화된 `.veb` 패키지까지, VillageSQL에서 사용자 정의 확장을 다시 만드는 과정을 안내합니다.

## 마이그레이션 접근 방식

1. PostgreSQL에서 **스키마를 내보내고**(`pg_dump --schema-only`) 위의 타입 매핑을 사용해 각 테이블을 수동으로 변환합니다.
2. PostgreSQL에서 **데이터를 CSV로 내보냅니다**(`COPY table TO '/tmp/table.csv' CSV HEADER`).
3. `LOAD DATA INFILE` 또는 `mysqlimport`로 MySQL에 **데이터를 로드합니다**.
4. **쿼리를 테스트합니다** — 모든 PostgreSQL 고유 구문을 찾아 다시 작성합니다.
5. **개수와 체크섬을 검증합니다** — 모든 테이블에 `SELECT COUNT(*)`; 주요 행을 표본 검사합니다.

## 자주 묻는 질문

#### MySQL은 PostgreSQL의 `ON CONFLICT` 같은 `UPSERT`를 지원하나요?

예, 다른 구문으로 지원합니다. [MySQL의 UPSERT](https://villagesql.com/docs/ko/guides/upsert.md)를 보세요. 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)이 더 폭넓은 도구 지원을 받습니다.

## 문제 해결

| 문제 | 해결 방법 | | |
| :- | :- | - | - |
| \` | | \` 연결에서 구문 오류 | `CONCAT()`로 교체 |
| `ILIKE`를 인식하지 못함 | `LIKE` 사용(ci 콜레이션에서 기본적으로 대소문자 구분 없음) | | |
| 큰따옴표 식별자가 동작하지 않음 | `ANSI_QUOTES` SQL 모드를 활성화하거나 백틱 인용으로 전환 | | |
| `RETURNING` 절이 동작하지 않음 | INSERT 뒤에 `LAST_INSERT_ID()` 사용; MySQL 등가물이 없음 | | |
| `SERIAL`이 Postgres와 다르게 동작 | MySQL은 `SERIAL`을 받아들임 — `BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE`의 별칭으로, Postgres의 `SERIAL`보다 넓고 암시적 고유 키를 동반함. 더 좁은 Postgres 등가물에는 명시적 `INT AUTO_INCREMENT` 사용 | | |
| 배열 열이 실패 | 외래 키가 있는 자식 테이블로 정규화 | | |

## 함께 보기

* [MySQL의 스키마 마이그레이션](https://villagesql.com/docs/ko/guides/schema-migrations.md) — 마이그레이션에 필요한 DDL 변경 실행하기
* [MySQL 데이터 타입 선택하기](https://villagesql.com/docs/ko/guides/choosing-data-types.md) — PostgreSQL 타입을 MySQL 등가물에 매핑하기
* [MySQL 대 PostgreSQL: 무엇을 사용해야 하나요?](https://villagesql.com/docs/ko/guides/mysql-vs-postgresql.md) — 마이그레이션 뒤에 있는 기능별 비교


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.