将 PostgreSQL 转换为 MySQL
拖入由 pg_dump 或 pg_dumpall 生成的纯文本转储(.sql 或 .sql.gz),即可下载包含相同表、键、索引和数据的 MySQL 脚本。MySQL 无法原样存储的每个值,以及它不支持的每个对象,都会在报告中列出。
已转换 18 行,3 处修正,无拒绝
shop_2024_postgres.sql 中的表PostgreSQL 18.6 转储 · 由 pg_dump 写出 · utf8mb4
customers8 行2
8 列 id int not null name text not null email varchar(768) not null country char(2) not null is_active tinyint(1) not null tags json not null signed_up date created_at datetime(6) not null orders10 行1
10 列 id int not null customer_id int not null status enum('pending','paid','shipped','cancelled') not null total decimal(10,2) not null items smallint not null ordered_at datetime(6) not null shipped_at datetime(6) note text details json delivery varchar(100)
未转换
- 视图 paid_orders
每种数据库都用自己的语言编写这些对象。请为 MySQL 重写它们。
-- Written by IntoSQL from a PostgreSQL dump. Load it with: mysql --default-character-set=utf8mb4 <database> < <file>
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
SET UNIQUE_CHECKS = 0;
SET SQL_MODE = 'NO_AUTO_VALUE_ON_ZERO,ALLOW_INVALID_DATES';
CREATE TABLE `customers` (
`id` int NOT NULL AUTO_INCREMENT,
`name` text NOT NULL,
`email` varchar(768) NOT NULL,
`country` char(2) NOT NULL DEFAULT 'US',
`is_active` tinyint(1) NOT NULL DEFAULT 1,
`tags` json NOT NULL,
`signed_up` date,
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
UNIQUE KEY `customers_email_key` (`email`),
PRIMARY KEY (`id`)
) AUTO_INCREMENT=1001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='People who bought at least once';
CREATE TABLE `orders` (
`id` int NOT NULL AUTO_INCREMENT,
`customer_id` int NOT NULL,
`status` enum('pending','paid','shipped','cancelled') NOT NULL DEFAULT 'pending',
`total` decimal(10,2) NOT NULL,
`items` smallint NOT NULL DEFAULT 1,
`ordered_at` datetime(6) NOT NULL,
`shipped_at` datetime(6),
`note` text,
`details` json,
`delivery` varchar(100),
PRIMARY KEY (`id`),
KEY `idx_orders_customer` (`customer_id`),
KEY `idx_orders_status_date` (`status`,`ordered_at`),
CONSTRAINT `orders_total_check` CHECK (`total` >= (0)),
CONSTRAINT `orders_customer_id_fkey` FOREIGN KEY (`customer_id`) REFERENCES `customers` (`id`)
) AUTO_INCREMENT=5001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
INSERT INTO `customers` (`id`,`name`,`email`,`country`,`is_active`,`tags`,`signed_up`,`created_at`) VALUES (1,'Ana Martínez','ana@example.com','MX',1,'["vip"]','2023-01-15','2023-01-15 10:24:00'),(2,'Zoë Müller','zoe@example.de','DE',1,'[]','2023-02-03','2023-02-03 08:11:42'),(3,'José Peña','jose@example.es','ES',0,'["wholesale","late payer"]','2023-03-21','2023-03-21 19:05:17'),(4,'李雷','lilei@example.cn','CN',1,'[]','2023-05-09','2023-05-09 02:40:00'),(5,'Noah O\'Brien','noah@example.ie','IE',1,'[]',NULL,'2023-06-30 14:00:00'),(6,'Amélie Laurent','amelie@example.fr','FR',1,'["vip"]','2024-01-02','2024-01-02 09:30:00'),(7,'Ravi Patel','ravi@example.in','IN',0,'[]','2024-02-29','2024-02-29 23:59:59'),(8,'Sofia Rossi 🌻','sofia@example.it','IT',1,'[]','2024-04-12','2024-04-12 12:12:12');
INSERT INTO `orders` (`id`,`customer_id`,`status`,`total`,`items`,`ordered_at`,`shipped_at`,`note`,`details`,`delivery`) VALUES (1,1,'shipped',129.90,3,'2024-03-01 10:00:00','2024-03-02 16:30:00',NULL,'{"gift": false}','1 day 06:30:00'),(2,2,'paid',54.00,1,'2024-03-02 11:15:00',NULL,'Gift wrap 🎁','{"gift": true, "wrap": "red"}',NULL),(3,1,'cancelled',12.50,1,'2024-03-03 09:45:00',NULL,'Customer changed their mind',NULL,NULL),(4,4,'shipped',980.00,12,'2024-03-05 02:10:00','2024-03-07 08:00:00','请小心轻放','{"fragile": true}','2 days 05:50:00'),(5,5,'pending',45.75,2,'2024-03-08 17:20:00',NULL,'Leave at the door; ring twice',NULL,NULL),(6,6,'shipped',310.40,5,'2024-03-10 13:00:00','2024-03-11 09:00:00','Fragile: "glass"','{"fragile": true}','20:00:00'),(7,8,'paid',76.20,2,'2024-03-11 18:45:00',NULL,NULL,NULL,NULL),(8,2,'shipped',19.99,1,'2024-03-12 08:00:00',NULL,'Shipped date set to infinity by an old import',NULL,NULL),(9,3,'shipped',64.00,2,'2024-03-14 15:30:00','2024-03-15 10:00:00','Two parcels','{"parcels": [1, 2]}','18:30:00'),(10,7,'paid',1499.00,1,'2024-03-15 21:05:00',NULL,'C:\\Users\\ravi\\invoice.pdf',NULL,NULL);
SET UNIQUE_CHECKS = 1;
SET FOREIGN_KEY_CHECKS = 1;
修正
- customers 的 "created_at" 包含带时区的时间点;MySQL 没有能保留时区的类型,因此以 UTC 存储为 datetime(6)。
- customers 的 "tags" 是 text[],MySQL 没有该类型,因此为 json,值保持不变。
- orders 的 "delivery" 是 interval,MySQL 没有该类型,因此为 varchar(100),值保持不变。
需要检查
- 1 个视图、触发器、例程或事件未转换;报告逐一列出了它们。 请在 MySQL 中手动重新创建它们。
- customers 中 "tags" 的默认值无法为 MySQL 写出(a value its type can't hold),因此该列没有默认值。 如果新行需要,请手动添加默认值。
- orders 上的索引 idx_orders_details 使用了无法为 MySQL 写出的表达式(neither MySQL 5.7 nor MariaDB index an expression),因此未创建。 如果查询需要,请手动创建该索引。
- orders 的 "shipped_at" 中有 1 个值是 MySQL 无法存储的日期,例如 infinity,因此设为 NULL。 MySQL 存储 1 到 9999 年,不支持无穷值;如果这些日期很重要,请在源数据中替换它们。
文件在这台电脑上转换,永远不会上传。
其他示例:
从 PostgreSQL 到 MySQL 的变化
每一行是转换器应用的一条规则,以及该规则更改你的数据时在报告中写入的记录。
boolean
tinyint(1)
MySQL 的 BOOLEAN 就是 TINYINT(1):true 为 1,false 为 0。
varchar(n), char(n), text
不超过 1024 个字符时为 varchar(n),不超过 255 时为 char(n),更长则为 text
文本以 utf8mb4 保留每个字符。键中的列最多为 varchar(768),这是键能容纳的最大长度;带默认值的文本也是如此,因为 MySQL 5.7 只允许 varchar 有默认值。
numeric(p,s) past 65 digits or 30 decimals, and numeric values past what decimal(65,30) holds
varchar
MySQL 的 decimal 可容纳 65 位数字,其中 30 位小数。更宽的数字保存为文本,使每一位都保持精确;不带精度的 numeric 在其值能容纳时为 decimal(65,30)。
报告记录: decimal_as_text
money
decimal(19,2)
MySQL 没有 money 类型。金额保存为数字,不带服务器区域设置添加的货币符号。
报告记录: type_replaced
interval
varchar(100),存放 PostgreSQL 的文本
MySQL 的 TIME 可容纳最长 838 小时的时长,但 interval 包含月和天;其文本保留每个部分。
报告记录: type_replaced
arrays (integer[], text[]...)
json 数组
MySQL 没有数组。JSON 数组保留每个元素和 NULL,JSON 函数可以读取它。
报告记录: type_replaced
ranges, geometric types, tsvector, xml, hstore, composite and extension types
longtext,存放 PostgreSQL 的文本
MySQL 没有对应的类型,因此每个值保留 PostgreSQL 写出的文本。
报告记录: type_replaced
json, jsonb
json
JSON 值相同;jsonb 的键顺序和空格由服务器决定。
timestamp
datetime(p)
不带时区的 timestamp 是挂钟读数,与 datetime 相同。
timestamp with time zone
UTC 的 datetime(p)
MySQL 的 TIMESTAMP 只到 2038 年,因此时间点以 UTC 存储为 datetime;无论转储写入的偏移量是多少,都是同一时刻。
报告记录: moments_as_utc
time
time(p)
MySQL 的 TIME 可容纳一天中的每个时刻,包括 24:00:00。
infinity, -infinity, BC dates and years past 9999
NULL
MySQL 存储 1000 到 9999 年,不支持无穷值。这些值变为 NULL,报告会统计其数量。
报告记录: date_out_of_range
NaN and Infinity in numeric, real and double precision
NULL
MySQL 的数字没有 NaN 或 Infinity,因此这些值变为 NULL,报告会统计其数量。
报告记录: invalid_value_null
NOT NULL columns with values that become NULL
去除 NOT NULL
值必须变为 NULL 的列无法保留 NOT NULL,否则导入会中止。
报告记录: not_null_relaxed
enum types
enum(...),标签相同
MySQL 的 ENUM 按原顺序保留标签。
uuid, inet, cidr
uuid 为 char(36),inet 和 cidr 为 varchar(43)
MySQL 没有 uuid 或地址类型;文本保留每个值。
bytea
blob、mediumblob 或 longblob;键中为 varbinary(3072)
字节仍然是字节。
bit(n)
不超过 64 位时为 bit(n),更长则为文本
不超过 64 位的位串存为其各位组成的数字;更长的位串保留其数字。
citext and nondeterministic collations
精确比较
表使用 utf8mb4_bin,因此文本像 PostgreSQL 一样精确比较;源数据中不区分大小写的列会失去这一点。
报告记录: case_insensitive_collation
serial, identity
AUTO_INCREMENT,从最大的 id 之后继续
由主键的第一列计数;计数器从序列所在的位置继续。
a second serial or identity column in a table
普通整数
MySQL 每个表只为一列计数;其他列保留其值,不带计数器。
报告记录: counter_dropped
sequences not owned by a column
不创建
MySQL 没有序列。使用序列计数的列改为 AUTO_INCREMENT。
报告记录: sequence_not_converted
generated columns
生成列,与源数据一样为 STORED
MySQL 以相同方式计算它们。
generated columns with functions MySQL lacks
普通列
无法为 MySQL 写出的公式会留下一个普通列和一条报告记录。
报告记录: generated_not_translated
defaults with functions MySQL lacks, and defaults from a sequence
无默认值
无法为 MySQL 写出的默认值会被省略,并在报告中列出。
报告记录: default_not_translated
CHECK constraints
已转换,内联在 CREATE TABLE 中
MySQL 8.0.16 和 MariaDB 会执行它们;MySQL 5.7 保留它们但不检查。
CHECK constraints with functions MySQL lacks
不创建
无法为 MySQL 写出的检查约束会被省略,并在报告中列出。
报告记录: check_skipped
CHECK ... NOT VALID
不创建
PostgreSQL 从未检查之前已有的行,因此它们可能违反该约束。
报告记录: check_not_valid
gin, gist, brin and hash indexes, partial and expression indexes
不创建
MySQL 只有 b-tree、全文和空间索引,建立在整列上,不带 WHERE。
报告记录: index_skipped
foreign keys
外键,以 FOREIGN_KEY_CHECKS=0 导入
行可以按任意顺序导入,之后外键会检查新行。
foreign keys to tables the dump doesn't create
不创建
外键无法引用不存在的表。
报告记录: foreign_key_skipped
partitioned tables
单个表,包含每个分区的行
行保持不变;如有需要,请在 MySQL 中重新对该表分区。
报告记录: partitions_dropped
schemas
单个数据库,每个表名前加上其 schema 名
MySQL 的 schema 就是数据库,而导入到单个数据库的脚本无法创建数据库;public 中的表保留原名。
报告记录: schemas_merged
views, functions, procedures, triggers, rules, policies, extensions and types
在报告中列出
它们的代码用 PostgreSQL 的语言编写,因此会列出来,由你重新创建。
报告记录: not_converted_objects
dumps without CREATE TABLE (--data-only)
根据值推断类型
只含数据的转储只有行,因此类型来自值。
报告记录: no_schema
expressions in INSERT values
NULL
不运行数据库的转换器无法对它们求值。
报告记录: expression_value
types IntoSQL doesn't know
longtext
文本可以容纳任何值;请在导入后更改类型。
报告记录: unknown_type
text that isn't valid in its encoding
错误的字节变为替换字符
文本必须是有效的 utf8mb4。
报告记录: invalid_utf8_replaced
text decoded twice (ñ)
按原样复制
转储中存储的正是这些字符,因此按原样复制;报告会指出它们。
报告记录: mojibake_suspected
| 在 PostgreSQL 中 | 在 MySQL 中 | 报告记录 |
|---|---|---|
| boolean | tinyint(1)MySQL 的 BOOLEAN 就是 TINYINT(1):true 为 1,false 为 0。 | |
| varchar(n), char(n), text | 不超过 1024 个字符时为 varchar(n),不超过 255 时为 char(n),更长则为 text文本以 utf8mb4 保留每个字符。键中的列最多为 varchar(768),这是键能容纳的最大长度;带默认值的文本也是如此,因为 MySQL 5.7 只允许 varchar 有默认值。 | |
| numeric(p,s) past 65 digits or 30 decimals, and numeric values past what decimal(65,30) holds | varcharMySQL 的 decimal 可容纳 65 位数字,其中 30 位小数。更宽的数字保存为文本,使每一位都保持精确;不带精度的 numeric 在其值能容纳时为 decimal(65,30)。 | decimal_as_text |
| money | decimal(19,2)MySQL 没有 money 类型。金额保存为数字,不带服务器区域设置添加的货币符号。 | type_replaced |
| interval | varchar(100),存放 PostgreSQL 的文本MySQL 的 TIME 可容纳最长 838 小时的时长,但 interval 包含月和天;其文本保留每个部分。 | type_replaced |
| arrays (integer[], text[]...) | json 数组MySQL 没有数组。JSON 数组保留每个元素和 NULL,JSON 函数可以读取它。 | type_replaced |
| ranges, geometric types, tsvector, xml, hstore, composite and extension types | longtext,存放 PostgreSQL 的文本MySQL 没有对应的类型,因此每个值保留 PostgreSQL 写出的文本。 | type_replaced |
| json, jsonb | jsonJSON 值相同;jsonb 的键顺序和空格由服务器决定。 | |
| timestamp | datetime(p)不带时区的 timestamp 是挂钟读数,与 datetime 相同。 | |
| timestamp with time zone | UTC 的 datetime(p)MySQL 的 TIMESTAMP 只到 2038 年,因此时间点以 UTC 存储为 datetime;无论转储写入的偏移量是多少,都是同一时刻。 | moments_as_utc |
| time | time(p)MySQL 的 TIME 可容纳一天中的每个时刻,包括 24:00:00。 | |
| infinity, -infinity, BC dates and years past 9999 | NULLMySQL 存储 1000 到 9999 年,不支持无穷值。这些值变为 NULL,报告会统计其数量。 | date_out_of_range |
| NaN and Infinity in numeric, real and double precision | NULLMySQL 的数字没有 NaN 或 Infinity,因此这些值变为 NULL,报告会统计其数量。 | invalid_value_null |
| NOT NULL columns with values that become NULL | 去除 NOT NULL值必须变为 NULL 的列无法保留 NOT NULL,否则导入会中止。 | not_null_relaxed |
| enum types | enum(...),标签相同MySQL 的 ENUM 按原顺序保留标签。 | |
| uuid, inet, cidr | uuid 为 char(36),inet 和 cidr 为 varchar(43)MySQL 没有 uuid 或地址类型;文本保留每个值。 | |
| bytea | blob、mediumblob 或 longblob;键中为 varbinary(3072)字节仍然是字节。 | |
| bit(n) | 不超过 64 位时为 bit(n),更长则为文本不超过 64 位的位串存为其各位组成的数字;更长的位串保留其数字。 | |
| citext and nondeterministic collations | 精确比较表使用 utf8mb4_bin,因此文本像 PostgreSQL 一样精确比较;源数据中不区分大小写的列会失去这一点。 | case_insensitive_collation |
| serial, identity | AUTO_INCREMENT,从最大的 id 之后继续由主键的第一列计数;计数器从序列所在的位置继续。 | |
| a second serial or identity column in a table | 普通整数MySQL 每个表只为一列计数;其他列保留其值,不带计数器。 | counter_dropped |
| sequences not owned by a column | 不创建MySQL 没有序列。使用序列计数的列改为 AUTO_INCREMENT。 | sequence_not_converted |
| generated columns | 生成列,与源数据一样为 STOREDMySQL 以相同方式计算它们。 | |
| generated columns with functions MySQL lacks | 普通列无法为 MySQL 写出的公式会留下一个普通列和一条报告记录。 | generated_not_translated |
| defaults with functions MySQL lacks, and defaults from a sequence | 无默认值无法为 MySQL 写出的默认值会被省略,并在报告中列出。 | default_not_translated |
| CHECK constraints | 已转换,内联在 CREATE TABLE 中MySQL 8.0.16 和 MariaDB 会执行它们;MySQL 5.7 保留它们但不检查。 | |
| CHECK constraints with functions MySQL lacks | 不创建无法为 MySQL 写出的检查约束会被省略,并在报告中列出。 | check_skipped |
| CHECK ... NOT VALID | 不创建PostgreSQL 从未检查之前已有的行,因此它们可能违反该约束。 | check_not_valid |
| gin, gist, brin and hash indexes, partial and expression indexes | 不创建MySQL 只有 b-tree、全文和空间索引,建立在整列上,不带 WHERE。 | index_skipped |
| foreign keys | 外键,以 FOREIGN_KEY_CHECKS=0 导入行可以按任意顺序导入,之后外键会检查新行。 | |
| foreign keys to tables the dump doesn't create | 不创建外键无法引用不存在的表。 | foreign_key_skipped |
| partitioned tables | 单个表,包含每个分区的行行保持不变;如有需要,请在 MySQL 中重新对该表分区。 | partitions_dropped |
| schemas | 单个数据库,每个表名前加上其 schema 名MySQL 的 schema 就是数据库,而导入到单个数据库的脚本无法创建数据库;public 中的表保留原名。 | schemas_merged |
| views, functions, procedures, triggers, rules, policies, extensions and types | 在报告中列出它们的代码用 PostgreSQL 的语言编写,因此会列出来,由你重新创建。 | not_converted_objects |
| dumps without CREATE TABLE (--data-only) | 根据值推断类型只含数据的转储只有行,因此类型来自值。 | no_schema |
| expressions in INSERT values | NULL不运行数据库的转换器无法对它们求值。 | expression_value |
| types IntoSQL doesn't know | longtext文本可以容纳任何值;请在导入后更改类型。 | unknown_type |
| text that isn't valid in its encoding | 错误的字节变为替换字符文本必须是有效的 utf8mb4。 | invalid_utf8_replaced |
| text decoded twice (ñ) | 按原样复制转储中存储的正是这些字符,因此按原样复制;报告会指出它们。 | mojibake_suspected |
将脚本导入 MySQL
脚本先创建表、复制行,再添加键、索引和外键,因此导入速度快,并会在遇到第一个错误时停止。
mysql --default-character-set=utf8mb4 mydb < postgresql.mysql.sql外键按转储中的声明创建,导入行时使用 FOREIGN_KEY_CHECKS=0,因此指向不存在的行的行不会中止导入。
对于较大的转储,请在服务器所在的机器上运行客户端;如果单行非常大,请调高 max_allowed_packet。
IntoSQL Pro 可以通过桌面应用或命令行将文件直接导入 MySQL,无需生成脚本。
如何转储 PostgreSQL 以获得最佳结果
转换器读取转储中的内容。以下选项能让转储保留更多数据:
- --format=plain 是 pg_dump 的默认格式,写出 SQL。custom 或 directory 归档(-Fc、-Fd)可用 pg_restore -f dump.sql 转为 SQL,无需数据库服务器。
- --no-owner --no-privileges 会省略角色和授权,转换器本来也会跳过它们,因此转储更小。
- pg_dumpall 将每个数据库转储到一个文件中;每个数据库的表名前会加上该数据库名,因此不会冲突。
哪些内容不会转换
视图、物化视图、函数、存储过程、触发器、规则、行安全策略、扩展和类型都是用 PostgreSQL 自己的语言编写的。它们会按名称在报告中列出,绝不会被悄悄丢弃,以便你为 MySQL 重写。
关于 PostgreSQL 转 MySQL 的问题
这里没有你要的答案?请通过联系页面给我们留言,会有专人回复。 联系页面
我的转储会被上传吗?
不会。转换器在你的浏览器中运行,分块从磁盘读取转储,因此转储不会离开你的电脑。桌面应用和命令行也在你的电脑上转换。
转储可以多大?
转换器从不把整个转储放进内存:它按范围读取文件两次,边读边写出脚本。几 GB 的转储可以在浏览器、桌面应用和命令行中转换。
它能读取 pg_dump -Fc 归档吗?
不能直接读取:custom 归档是二进制格式。pg_restore -f dump.sql dump.backup 会将其转为转换器读取的纯 SQL,无需连接服务器。转换器收到归档时会给出提示。
转储可以来自哪些 PostgreSQL 版本?
由 PostgreSQL 10 及更高版本的 pg_dump 和 pg_dumpall 写出的转储,行可以是 COPY 或 INSERT。每个测试转储都来自 PostgreSQL 13 和 18,并与其源数据逐个单元格比较。
serial 和标识列的值会保留吗?
会,每个表一列:作为主键第一列的 serial 或标识列变为 AUTO_INCREMENT,从最大的 id 之后继续。MySQL 每个表只为一列计数,因此其他列保留其值,不带计数器,报告会说明这一点。
数组、enum 和其他 PostgreSQL 类型会怎样处理?
数组变为 JSON 数组,布尔值变为 tinyint(1),enum 变为标签相同的 ENUM,money 变为 decimal。range、几何类型、hstore 和复合类型保留 PostgreSQL 的文本,报告会说明是哪些列。
函数、触发器和视图怎么处理?
它们会按名称在报告中列出,但不会转换,因为每种数据库都用自己的语言编写它们。
脚本可以导入哪些 MySQL 版本?
MySQL 5.7 及更高版本。每个测试转储都能在 5.7 和 9 上导入,如有问题会在第一个错误处停止。