跳到正文
IntoSQL 首页

将 PostgreSQL 转换为 MySQL

拖入由 pg_dump 或 pg_dumpall 生成的纯文本转储(.sql 或 .sql.gz),即可下载包含相同表、键、索引和数据的 MySQL 脚本。MySQL 无法原样存储的每个值,以及它不支持的每个对象,都会在报告中列出。

你的文件
sqlshop_2024_postgres.sql示例文件,6.2 kB
你的数据库

已转换 18 行,3 处修正,无拒绝

显示

shop_2024_postgres.sql 中的表PostgreSQL 18.6 转储 · 由 pg_dump 写出 · utf8mb4

  • customers8 行2
    8 列
    idint not null
    nametext not null
    emailvarchar(768) not null
    countrychar(2) not null
    is_activetinyint(1) not null
    tagsjson not null
    signed_update
    created_atdatetime(6) not null
  • orders10 行1
    10 列
    idint not null
    customer_idint not null
    statusenum('pending','paid','shipped','cancelled') not null
    totaldecimal(10,2) not null
    itemssmallint not null
    ordered_atdatetime(6) not null
    shipped_atdatetime(6)
    notetext
    detailsjson
    deliveryvarchar(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;

修正

  1. customers 的 "created_at" 包含带时区的时间点;MySQL 没有能保留时区的类型,因此以 UTC 存储为 datetime(6)。
  2. customers 的 "tags" 是 text[],MySQL 没有该类型,因此为 json,值保持不变。
  3. 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 中报告记录
booleantinyint(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) holdsvarcharMySQL 的 decimal 可容纳 65 位数字,其中 30 位小数。更宽的数字保存为文本,使每一位都保持精确;不带精度的 numeric 在其值能容纳时为 decimal(65,30)。decimal_as_text
moneydecimal(19,2)MySQL 没有 money 类型。金额保存为数字,不带服务器区域设置添加的货币符号。type_replaced
intervalvarchar(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 typeslongtext,存放 PostgreSQL 的文本MySQL 没有对应的类型,因此每个值保留 PostgreSQL 写出的文本。type_replaced
json, jsonbjsonJSON 值相同;jsonb 的键顺序和空格由服务器决定。
timestampdatetime(p)不带时区的 timestamp 是挂钟读数,与 datetime 相同。
timestamp with time zoneUTC 的 datetime(p)MySQL 的 TIMESTAMP 只到 2038 年,因此时间点以 UTC 存储为 datetime;无论转储写入的偏移量是多少,都是同一时刻。moments_as_utc
timetime(p)MySQL 的 TIME 可容纳一天中的每个时刻,包括 24:00:00。
infinity, -infinity, BC dates and years past 9999NULLMySQL 存储 1000 到 9999 年,不支持无穷值。这些值变为 NULL,报告会统计其数量。date_out_of_range
NaN and Infinity in numeric, real and double precisionNULLMySQL 的数字没有 NaN 或 Infinity,因此这些值变为 NULL,报告会统计其数量。invalid_value_null
NOT NULL columns with values that become NULL去除 NOT NULL值必须变为 NULL 的列无法保留 NOT NULL,否则导入会中止。not_null_relaxed
enum typesenum(...),标签相同MySQL 的 ENUM 按原顺序保留标签。
uuid, inet, cidruuid 为 char(36),inet 和 cidr 为 varchar(43)MySQL 没有 uuid 或地址类型;文本保留每个值。
byteablob、mediumblob 或 longblob;键中为 varbinary(3072)字节仍然是字节。
bit(n)不超过 64 位时为 bit(n),更长则为文本不超过 64 位的位串存为其各位组成的数字;更长的位串保留其数字。
citext and nondeterministic collations精确比较表使用 utf8mb4_bin,因此文本像 PostgreSQL 一样精确比较;源数据中不区分大小写的列会失去这一点。case_insensitive_collation
serial, identityAUTO_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 valuesNULL不运行数据库的转换器无法对它们求值。expression_value
types IntoSQL doesn't knowlongtext文本可以容纳任何值;请在导入后更改类型。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 上导入,如有问题会在第一个错误处停止。