---
title: "MySQL에서 PostgreSQL로 변환"
description: "MySQL나 MariaDB 덤프를 한 번에 불러와지는 PostgreSQL 스크립트로 변환하고, 모든 변경을 보고서에 담아요. 브라우저에서 처리해 덤프는 업로드되지 않아요."
canonical: https://intosql.com/ko/mysql-to-postgresql
last_updated: 2026-10-15
---

# MySQL에서 PostgreSQL로 변환

mysqldump, mariadb-dump, mysqlpump, phpMyAdmin, Adminer에서 만든 .sql 또는 .sql.gz 덤프를 놓으면, 같은 테이블, 키, 인덱스, 데이터를 담은 PostgreSQL 스크립트를 다운로드할 수 있어요. PostgreSQL이(가) 그대로 담을 수 없는 값과 옮기지 않는 객체는 모두 보고서에 나와요.

## MySQL에서 PostgreSQL로 바뀌는 점

각 행은 변환기가 적용하는 규칙이며, 규칙이 데이터를 바꿀 때 보고서에 쓰는 줄이 함께 나와요.

| MySQL에서 | PostgreSQL에서 | 보고서 줄 |
| --- | --- | --- |
| TINYINT(1), BOOL | 모든 값이 0 또는 1이면 boolean, 아니면 smallint | `bool_kept_numeric` |
| UNSIGNED integers | 음수가 아닌지 확인하는 CHECK가 붙은 한 단계 넓은 정수. BIGINT UNSIGNED는 모든 값이 들어가면 bigint, 아니면 numeric(20,0) | `unsigned_widened` |
| ZEROFILL and display widths | 제거 | `zerofill_dropped` |
| FLOAT(M,D) | real 또는 double precision, 소수 자릿수 없이 | `float_scale_dropped` |
| BIT(1), BIT(n) | boolean, bit(n) |  |
| zero, partial or impossible dates (0000-00-00, 2024-02-30) | NULL | `invalid_date_null` |
| NOT NULL columns with values that become NULL | NOT NULL 제거 | `not_null_relaxed` |
| DEFAULT '0000-00-00' | 기본값 없음 | `zero_date_default` |
| TIME | 모든 값이 하루 안이면 time, 아니면 interval | `time_as_interval` |
| YEAR | 범위 CHECK가 붙은 smallint |  |
| CHAR(n) | varchar(n) |  |
| DATETIME | timestamp |  |
| TIMESTAMP | 덤프에 명시된 시간대의 timestamptz, 명시가 없으면 UTC | `timestamp_zone_assumed` |
| ENUM | 값 CHECK가 붙은 text |  |
| ENUM holding '' (MySQL's error value) | 체크 제약이 허용하는 빈 값 | `enum_invalid_value` |
| ENUM or SET holding values outside its list | CHECK 없음 | `enum_check_skipped` |
| SET | 모든 요소가 목록에 있는지 확인하는 CHECK가 붙은 text[] |  |
| JSON | jsonb, 값에 \u0000이 있으면 json | `json_not_jsonb` |
| NUL bytes in text | 제거 | `nul_removed` |
| text that isn't valid in its character set | 잘못된 바이트를 대체 문자로 | `invalid_utf8_replaced` |
| text decoded twice (Ã±) | 그대로 복사 | `mojibake_suspected` |
| AUTO_INCREMENT | GENERATED BY DEFAULT AS IDENTITY, 카운터는 복사된 id 다음부터 |  |
| MyISAM AUTO_INCREMENT on the second column of a key | 테이블당 카운터 하나 | `myisam_group_autoincrement` |
| ON UPDATE CURRENT_TIMESTAMP | BEFORE UPDATE 트리거 | `on_update_trigger` |
| generated columns | STORED | `virtual_as_stored` |
| generated columns with functions PostgreSQL lacks | 일반 열 | `generated_not_translated` |
| INVISIBLE columns | 일반 열 | `invisible_column` |
| CHECK constraints | 변환해서 CREATE TABLE 안에 인라인으로 |  |
| CHECK constraints with functions PostgreSQL lacks | 만들지 않음 | `check_skipped` |
| CHECK ... NOT ENFORCED | 만들지 않음 | `check_not_enforced` |
| defaults with functions PostgreSQL lacks | 기본값 없음 | `default_not_translated` |
| keys and indexes on a prefix, c(20) | left(c, 20) 인덱스 | `prefix_unique` |
| FULLTEXT indexes | to_tsvector('simple', ...)에 대한 GIN | `fulltext_as_gin` |
| indexes on values over 2,000 bytes | hash | `index_on_wide_values` |
| SPATIAL indexes | PostGIS 없이는 만들지 않음 | `spatial_index_skipped` |
| functional indexes PostgreSQL can't write | 만들지 않음 | `index_skipped` |
| foreign keys | 데이터 뒤에 NOT VALID로 만들고, 고아 행은 경고와 함께 검증 |  |
| foreign keys to columns without a unique key | 만들지 않음 | `foreign_key_skipped` |
| table and column names | 고유성이 유지되면 소문자 | `identifier_renamed` |
| names over 63 bytes | 해시를 붙여 줄임 | `name_shortened` |
| GEOMETRY, POINT, POLYGON... | bytea에 WKB로 | `geometry_as_wkb` |
| INET4, INET6, UUID (MariaDB) | inet, uuid |  |
| BINARY, VARBINARY, BLOB | bytea |  |
| PARTITION BY | 하나의 테이블 | `partitions_dropped` |
| WITH SYSTEM VERSIONING (MariaDB) | 현재 행만 담은 테이블 | `system_versioning_dropped` |
| MyISAM, MEMORY and other engines | 일반 테이블 | `engine_dropped` |
| views, triggers, procedures, functions and events | 보고서에 나열 | `not_converted_objects` |
| dumps without CREATE TABLE | 값으로 판단한 타입 | `no_schema` |
| geometries in a dump without CREATE TABLE | bytea에 WKB로 | `geometry_inferred` |
| _ci collations | 정확한 비교 | `case_insensitive_collation` |
| SET NAMES utf8 from utf8mb4 tables | 변환하고 경고 표시 | `dump_lost_four_byte_chars` |
| dumps without SET NAMES whose text isn't UTF-8 | Latin-1 (Windows-1252)로 읽음 | `charset_guessed` |
| sjis, cp932, gbk, big5 dumps | UTF-8로 읽고 경고 표시 | `unsupported_charset` |
| UNHEX('..'), CONVERT(b'..', UNSIGNED), CONVERT(".." USING x), ST_GeomFromText('..') in rows | 계산함 |  |
| other expressions in rows | NULL | `expression_value` |
| DEFAULT in VALUES | 열의 기본값 | `default_keyword_filled` |
| DEFAULT in VALUES without a literal default | NULL | `default_keyword_null` |
| REPLACE and INSERT IGNORE | 일반 행 | `replace_as_insert` |
| types IntoSQL doesn't know | text | `unknown_type` |
| values that don't fit their column | NULL | `invalid_value_null` |
| unreadable geometries | NULL | `bad_geometry` |
| CREATE SEQUENCE (MariaDB) | 현재 값을 포함한 CREATE SEQUENCE |  |

## 스크립트를 PostgreSQL에 불러오기

스크립트는 테이블을 만들고 행을 복사한 다음 키, 인덱스, 외래 키를 추가해요. 그래서 빨리 불러오고 첫 오류에서 멈춰요.

```shell
psql -v ON_ERROR_STOP=1 -d mydb -f mysql.postgresql.sql
```

외래 키는 NOT VALID로 추가한 다음 검증해요. 어떤 행이 존재하지 않는 행을 가리키면, 중간에 실패하지 않고 어떤 제약인지 알려 줘요.

큰 덤프는 데이터베이스를 먼저 만들고, 서버와 같은 컴퓨터에서 psql로 스크립트를 실행하세요.

## 좋은 결과를 위한 MySQL 덤프 방법

변환기는 덤프에 담긴 내용을 읽어요. 다음 옵션을 쓰면 더 많은 데이터가 덤프에 남아요:

- --default-character-set=utf8mb4는 이모지와 기타 4바이트 문자를 유지해요. utf8 덤프는 변환기가 보기 전에 이 문자를 잃어요.
- --hex-blob은 바이너리 열을 16진수로 써서, 시스템 간에 복사해도 손상되지 않아요.
- --routines --triggers --events는 이 객체들을 덤프에 추가해서 보고서에 나열할 수 있게 해요.

## 변환되지 않는 것

뷰, 트리거, 저장 프로시저, 함수, 이벤트는 데이터베이스마다 자체 언어로 작성돼요. 보고서에 이름으로 나열되고 몰래 빠지는 일은 없으므로, PostgreSQL에 맞게 다시 작성할 수 있어요.

## pgloader와 비교한 측정 결과

같은 덤프를 2026년 10월에 한 컴퓨터(SATA SSD를 단 10코어 i9-10900K)에서 IntoSQL과 pgloader 3.6.7로 변환하고 불러왔어요.

- 2, -1, -5를 담은 TINYINT(1) 열: pgloader는 경고 없이 모두 true로 불러와요. IntoSQL은 숫자를 유지하고 보고서에 이유가 나와요.
- 기본값이 'it's'인 열, 백틱이 들어간 이름의 테이블, 빈 값을 담은 ENUM, 24시간이 넘는 TIME이 있는 덤프: pgloader는 멈춰요. IntoSQL은 변환하고 불러와요.
- pgloader로 불러온 결과에는 기본 키, 고유 키, 외래 키와 IDENTITY가 사라져요. IntoSQL은 모두 유지해요.
- 3,000만 행이 담긴 5.3 GB 덤프: pgloader는 먼저 실행 중인 MySQL에 복원해야 해서 18분이 걸리고, 그 뒤 기본 설정으로는 메모리가 부족해져요. IntoSQL은 덤프를 10초 만에 변환하고, psql은 약 3분 만에 스크립트를 불러와요.
- MySQL 5.5부터 9까지, MariaDB 10.3부터 12까지의 덤프 476개를 PostgreSQL 13과 18에 불러온 결과: 불러오기 오류는 없었고, 보고서에 기록 없이 바뀐 값도 없었어요.

## MySQL에서 PostgreSQL로 변환에 관한 질문

### 덤프가 업로드되나요?

아니요. 변환기는 브라우저에서 실행되고 디스크의 덤프를 조각조각 읽으므로, 덤프가 컴퓨터 밖으로 나가지 않아요. 데스크톱 앱과 명령줄도 컴퓨터에서 변환해요.

### 덤프는 얼마나 커도 되나요?

변환기는 덤프 전체를 메모리에 올리지 않아요. 파일을 범위별로 두 번 읽고, 읽는 대로 스크립트를 써요. 수 GB 덤프도 브라우저, 데스크톱 앱, 명령줄에서 변환돼요.

### MariaDB 덤프도 읽나요?

네. mariadb-dump와 MariaDB 10.3부터 12까지의 덤프도 같은 방식으로 변환되며, MariaDB의 INET6, INET4, UUID 열도 포함돼요.

### 0000-00-00 날짜는 어떻게 되나요?

PostgreSQL에는 0 날짜가 없어서 NULL이 되고, 열은 NULL을 허용하게 돼요. 열마다 몇 개였는지 보고서에 나와요.

### AUTO_INCREMENT 값은 유지되나요?

네. 각 AUTO_INCREMENT 열은 IDENTITY 열이 되고, 모든 행은 id를 유지해요. 다음 id는 가장 큰 id 다음부터 이어지고, 테이블의 AUTO_INCREMENT가 더 크면 그 다음부터 이어져요.

### TINYINT(1)은 boolean이 되나요?

열의 모든 값이 0 또는 1일 때만 그래요. 2나 -1 같은 다른 숫자가 있으면 숫자로 남고 보고서에 이유가 나와요. 그래서 실수로 true가 되는 값은 없어요.

### 저장 프로시저와 트리거는 어떻게 되나요?

데이터베이스마다 자체 언어로 작성하므로 변환하지 않고 보고서에 이름으로 나열해요. 예외는 ON UPDATE CURRENT_TIMESTAMP로, PostgreSQL 트리거가 돼요.

### 스크립트는 어떤 PostgreSQL 버전에서 불러올 수 있나요?

PostgreSQL 13부터 18까지예요. 모든 버전에서 같은 스크립트를 불러올 수 있고, 문제가 생기면 첫 오류에서 멈춰요.

## PostgreSQL로 변환하는 다른 방법

- [Excel에서 PostgreSQL로 변환](https://intosql.com/ko/excel-to-postgresql)
- [CSV에서 PostgreSQL로 변환](https://intosql.com/ko/csv-to-postgresql)
- [JSON에서 PostgreSQL로 변환](https://intosql.com/ko/json-to-postgresql)
