---
title: "将 MySQL 转换为 PostgreSQL"
description: "将 MySQL 或 MariaDB 转储转换为一次即可导入的 PostgreSQL 脚本，并附上每处更改的报告。在浏览器中完成，转储不会上传。"
canonical: https://intosql.com/zh-cn/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 | smallint，并带有限定范围的 CHECK |  |
| CHAR(n) | varchar(n) |  |
| DATETIME | timestamp |  |
| TIMESTAMP | 使用转储声明的时区的 timestamptz，未声明时为 UTC | `timestamp_zone_assumed` |
| ENUM | text，并带有限定其值的 CHECK |  |
| ENUM holding '' (MySQL's error value) | 检查约束允许的空值 | `enum_invalid_value` |
| ENUM or SET holding values outside its list | 不加 CHECK | `enum_check_skipped` |
| SET | text[]，并带有每个元素都在列表中的 CHECK |  |
| 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... | WKB，存于 bytea | `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 | WKB，存于 bytea | `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 会保留 emoji 和其他 4 字节字符，utf8 转储会在转换器看到之前就丢失它们。
- --hex-blob 将二进制列写为十六进制，在系统之间复制时不会损坏。
- --routines --triggers --events 将这些对象加入转储，以便报告列出它们。

## 哪些内容不会转换

视图、触发器、存储过程、函数和事件都是用各数据库自己的语言编写的。它们会按名称在报告中列出，绝不会被悄悄丢弃，以便你为 PostgreSQL 重写。

## 与 pgloader 的实测对比

2026 年 10 月，在同一台机器（10 核 i9-10900K，配 SATA SSD）上，用 IntoSQL 和 pgloader 3.6.7 转换并导入相同的转储。

- 一个包含 2、-1 和 -5 的 TINYINT(1) 列：pgloader 将每个值都导入为 true，且没有警告。IntoSQL 保留这些数字，报告会说明原因。
- 一个转储包含默认值 'it's'、名称带反引号的表、包含空值的 ENUM 以及超过 24 小时的 TIME：pgloader 会中止。IntoSQL 能转换并导入它。
- pgloader 导入的结果丢失了主键、唯一键、外键和标识列。IntoSQL 全部保留。
- 一个 5.3 GB、3000 万行的转储：pgloader 需要先将其恢复到运行中的 MySQL，耗时 18 分钟，之后在默认设置下内存不足。IntoSQL 在 10 秒内转换该转储，psql 导入脚本约需 3 分钟。
- 476 个来自 MySQL 5.5 到 9 和 MariaDB 10.3 到 12 的转储，导入 PostgreSQL 13 和 18：没有导入错误，也没有任何值在报告中无记录的情况下被更改。

## 关于 MySQL 转 PostgreSQL 的问题

### 我的转储会被上传吗？

不会。转换器在你的浏览器中运行，分块从磁盘读取转储，因此转储不会离开你的电脑。桌面应用和命令行也在你的电脑上转换。

### 转储可以多大？

转换器从不把整个转储放进内存：它按范围读取文件两次，边读边写出脚本。几 GB 的转储可以在浏览器、桌面应用和命令行中转换。

### 它能读取 MariaDB 转储吗？

能。来自 mariadb-dump 以及 MariaDB 10.3 到 12 的转储都以相同方式转换，包括 MariaDB 的 INET6、INET4 和 UUID 列。

### 0000-00-00 日期会怎样处理？

PostgreSQL 没有零日期，因此它们变为 NULL，该列也允许 NULL。报告会说明每列中有多少个。

### AUTO_INCREMENT 值会保留吗？

会。每个 AUTO_INCREMENT 列变为标识列，每行保留其 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/zh-cn/excel-to-postgresql)
- [将 CSV 转换为 PostgreSQL](https://intosql.com/zh-cn/csv-to-postgresql)
- [将 JSON 转换为 PostgreSQL](https://intosql.com/zh-cn/json-to-postgresql)
