跳到正文

将 MySQL 转换为 PostgreSQL

拖入由 mysqldump、mariadb-dump、mysqlpump、phpMyAdmin 或 Adminer 生成的 .sql 或 .sql.gz 转储,即可下载包含相同表、键、索引和数据的 PostgreSQL 脚本。PostgreSQL 无法原样存储的每个值,以及它不支持的每个对象,都会在报告中列出。

你的文件
sqlshop_2024.sql示例文件,5.1 kB
你的数据库

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

显示

shop_2024.sql 中的表MySQL 8.0.46 转储 · 由 mysqldump 写出 · utf8mb4

  • customers8 行3
    7 列
    idbigint not null
    namevarchar(100) not null
    emailvarchar(150) not null
    countryvarchar(2) not null
    is_activeboolean not null
    signed_update
    created_attimestamptz(0) not null
  • orders10 行5
    9 列
    idbigint not null
    customer_idbigint not null
    statustext not null
    totalnumeric(10,2) not null
    itemsinteger not null
    ordered_attimestamp(0) not null
    shipped_attimestamp(0)
    notevarchar(255)
    updated_attimestamptz(0) not null
-- Written by IntoSQL from a MySQL dump. Load it with: psql -v ON_ERROR_STOP=1 -f <file>
SET client_encoding = 'UTF8';
SET standard_conforming_strings = on;
SET check_function_bodies = false;
SET client_min_messages = warning;
SET synchronous_commit = off;
SET maintenance_work_mem = '512MB';

CREATE TABLE customers (
    id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL,
    name varchar(100) NOT NULL,
    email varchar(150) NOT NULL,
    country varchar(2) DEFAULT 'US' NOT NULL,
    is_active boolean DEFAULT true NOT NULL,
    signed_up date,
    created_at timestamptz(0) DEFAULT CURRENT_TIMESTAMP NOT NULL,
    CONSTRAINT customers_id_check CHECK (id >= 0)
);

CREATE TABLE orders (
    id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL,
    customer_id bigint NOT NULL,
    status text DEFAULT 'pending' NOT NULL,
    total numeric(10,2) NOT NULL,
    items integer DEFAULT 1 NOT NULL,
    ordered_at timestamp(0) NOT NULL,
    shipped_at timestamp(0),
    note varchar(255),
    updated_at timestamptz(0) DEFAULT CURRENT_TIMESTAMP NOT NULL,
    CONSTRAINT orders_id_check CHECK (id >= 0),
    CONSTRAINT orders_customer_id_check CHECK (customer_id >= 0),
    CONSTRAINT orders_status_check CHECK (status IN ('pending', 'paid', 'shipped', 'cancelled', '')),
    CONSTRAINT orders_items_check CHECK (items >= 0)
);

COPY customers (id, name, email, country, is_active, signed_up, created_at) FROM stdin;
1	Ana Martínez	ana@example.com	MX	t	2023-01-15	2023-01-15 10:24:00+00:00
2	Zoë Müller	zoe@example.de	DE	t	2023-02-03	2023-02-03 08:11:42+00:00
3	José Peña	jose@example.es	ES	f	2023-03-21	2023-03-21 19:05:17+00:00
4	李雷	lilei@example.cn	CN	t	2023-05-09	2023-05-09 02:40:00+00:00
5	Noah O'Brien	noah@example.ie	IE	t	\N	2023-06-30 14:00:00+00:00
6	Amélie Laurent	amelie@example.fr	FR	t	2024-01-02	2024-01-02 09:30:00+00:00
7	Ravi Patel	ravi@example.in	IN	f	2024-02-29	2024-02-29 23:59:59+00:00
8	Sofia Rossi 🌻	sofia@example.it	IT	t	2024-04-12	2024-04-12 12:12:12+00:00
\.

COPY orders (id, customer_id, status, total, items, ordered_at, shipped_at, note, updated_at) FROM stdin;
1	1	shipped	129.90	3	2024-03-01 10:00:00	2024-03-02 16:30:00	\N	2024-03-02 16:30:00+00:00
2	2	paid	54.00	1	2024-03-02 11:15:00	\N	Gift wrap 🎁	2024-03-02 11:20:00+00:00
3	1	cancelled	12.50	1	2024-03-03 09:45:00	\N	Customer changed their mind	2024-03-03 10:00:00+00:00
4	4	shipped	980.00	12	2024-03-05 02:10:00	2024-03-07 08:00:00	请小心轻放	2024-03-07 08:00:00+00:00
5	5	pending	45.75	2	2024-03-08 17:20:00	\N	Leave at the door; ring twice	2024-03-08 17:20:00+00:00
6	6	shipped	310.40	5	2024-03-10 13:00:00	2024-03-11 09:00:00	Fragile: "glass"	2024-03-11 09:00:00+00:00
7	8	paid	76.20	2	2024-03-11 18:45:00	\N	\N	2024-03-11 18:46:00+00:00
8	2		19.99	1	2024-03-12 08:00:00	\N	Status lost in an old import	2024-03-12 08:00:00+00:00
9	3	shipped	64.00	2	2024-03-14 15:30:00	\N	Shipping date never recorded	2024-03-15 10:00:00+00:00
10	7	paid	1499.00	1	2024-03-15 21:05:00	\N	C:\\Users\\ravi\\invoice.pdf	2024-03-15 21:05:00+00:00
\.


-- Keys, indexes and constraints, after the rows so each builds once.
CREATE UNIQUE INDEX customers_pkey ON customers (id);
CREATE UNIQUE INDEX customers_uq_email ON customers (email);
CREATE UNIQUE INDEX orders_pkey ON orders (id);
CREATE INDEX orders_idx_customer ON orders (customer_id);
CREATE INDEX orders_idx_status_date ON orders (status, ordered_at);
ALTER TABLE customers ADD CONSTRAINT customers_pkey PRIMARY KEY USING INDEX customers_pkey;
ALTER TABLE customers ADD CONSTRAINT customers_uq_email UNIQUE USING INDEX customers_uq_email;
ALTER TABLE orders ADD CONSTRAINT orders_pkey PRIMARY KEY USING INDEX orders_pkey;
ALTER TABLE orders ADD CONSTRAINT fk_orders_customer FOREIGN KEY (customer_id) REFERENCES customers (id) NOT VALID;
DO $f$ BEGIN
    ALTER TABLE orders VALIDATE CONSTRAINT fk_orders_customer;
EXCEPTION WHEN foreign_key_violation THEN
    RAISE WARNING 'foreign key % has rows without a parent: it stays NOT VALID, and new rows are checked', 'fk_orders_customer';
END $f$;
SELECT setval(pg_get_serial_sequence('customers', 'id'), LEAST(GREATEST(COALESCE((SELECT max(id) FROM customers)::numeric, 0) + 1, 1001), 9223372036854775807)::bigint, false);
COMMENT ON TABLE customers IS 'People who bought at least once';
SELECT setval(pg_get_serial_sequence('orders', 'id'), LEAST(GREATEST(COALESCE((SELECT max(id) FROM orders)::numeric, 0) + 1, 5001), 9223372036854775807)::bigint, false);
CREATE FUNCTION orders_updated_at_on_update() RETURNS trigger LANGUAGE plpgsql AS $f$
BEGIN
    IF NEW IS DISTINCT FROM OLD AND NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at THEN
        NEW.updated_at := CURRENT_TIMESTAMP;
    END IF;
    RETURN NEW;
END
$f$;
CREATE TRIGGER orders_updated_at_on_update BEFORE UPDATE ON orders FOR EACH ROW EXECUTE FUNCTION orders_updated_at_on_update();

修正

  1. customers 的 "id" 是无符号的,因此变为 bigint,并带有不能为负数的检查约束。
  2. customers 的 "signed_up" 现在允许 NULL:它的部分值无法存储,已设为 NULL。
  3. customers 中 "signed_up" 的默认值 0000‑00‑00 不是 PostgreSQL 接受的日期,因此该列没有默认值。
  4. orders 的 "customer_id" 是无符号的,因此变为 bigint,并带有不能为负数的检查约束。
  5. orders 的 "id" 是无符号的,因此变为 bigint,并带有不能为负数的检查约束。
  6. orders 的 "items" 是无符号的,因此变为 integer,并带有不能为负数的检查约束。
  7. orders 中 "status" 有 1 行包含 MySQL 为无效输入存储的空值;对其值的检查约束允许该值。
  8. orders 的 "updated_at" 在每次更新时变为当前时间;在 PostgreSQL 中由触发器完成。

需要检查

  • customers 的 "signed_up" 中有 1 个值是零日期或不可能的日期,例如 0000‑00‑00,PostgreSQL 无法存储,因此设为 NULL。 如果这些日期有意义,请在转储前于源数据中替换为真实日期。
  • orders 的 "shipped_at" 中有 1 个值是零日期或不可能的日期,例如 0000‑00‑00 00:00:00,PostgreSQL 无法存储,因此设为 NULL。 如果这些日期有意义,请在转储前于源数据中替换为真实日期。

文件在这台电脑上转换,永远不会上传。

其他示例:

从 MySQL 到 PostgreSQL 的变化

每一行是转换器应用的一条规则,以及该规则更改你的数据时在报告中写入的记录。

  • TINYINT(1), BOOL

    所有值都是 0 或 1 时为 boolean,否则为 smallint

    MySQL 的 BOOL 是 TINYINT(1),可以存储 -128 到 127 之间的任何数字。只包含 0 和 1 的列变为 boolean;包含 2 或 -1 的列保持为数字,因此没有值会改变含义。

    报告记录: bool_kept_numeric

  • UNSIGNED integers

    更宽一级的整数,并带有不能为负数的 CHECK;所有值都能容纳时 BIGINT UNSIGNED 为 bigint,否则为 numeric(20,0)

    PostgreSQL 没有无符号整数。更宽的类型能容纳整个无符号范围,检查约束像 MySQL 一样阻止负值。

    报告记录: unsigned_widened

  • ZEROFILL and display widths

    去除

    补零只是数字的显示方式,不是数字的一部分。数字会保留;如果需要补零,请在读取时格式化。

    报告记录: zerofill_dropped

  • FLOAT(M,D)

    real 或 double precision,不带小数位数

    PostgreSQL 的浮点数没有固定的小数位数。已存储的值按原样复制。

    报告记录: float_scale_dropped

  • BIT(1), BIT(n)

    boolean, bit(n)

    单个位表示是或否;更宽的位字段保留每一位。

  • zero, partial or impossible dates (0000-00-00, 2024-02-30)

    NULL

    PostgreSQL 只存储真实的日历日期。当 SQL 模式允许时,MySQL 会保留零日期和不可能的日期;它们会变为 NULL,报告会统计其数量。

    报告记录: invalid_date_null

  • NOT NULL columns with values that become NULL

    去除 NOT NULL

    值必须变为 NULL 的列无法保留 NOT NULL,否则导入会中止。

    报告记录: not_null_relaxed

  • DEFAULT '0000-00-00'

    无默认值

    PostgreSQL 无法存储的默认值会被省略,而不是替换为编造的日期。

    报告记录: zero_date_default

  • TIME

    所有值都在一天以内时为 time,否则为 interval

    MySQL 的 TIME 是从 -838:59:59 到 838:59:59 的时长。值都在一天以内的列保持为一天中的时刻;有更长或负值的列变为 interval。

    报告记录: time_as_interval

  • YEAR

    smallint,并带有限定范围的 CHECK

    PostgreSQL 没有年份类型;检查约束只允许 MySQL 允许的那些值。

  • CHAR(n)

    varchar(n)

    MySQL 读取 CHAR 值时会去掉末尾空格,而 PostgreSQL 的 char(n) 会把它们加回来,因此用 varchar(n) 按 MySQL 返回的样子保留值。

  • DATETIME

    timestamp

    DATETIME 是不带时区的挂钟读数,与 PostgreSQL 的 timestamp 相同。

  • TIMESTAMP

    使用转储声明的时区的 timestamptz,未声明时为 UTC

    TIMESTAMP 是一个时间点。转储会声明写入时使用的时区(mysqldump 写入 UTC);没有声明时,值按 UTC 读取,报告会说明这一点。

    报告记录: timestamp_zone_assumed

  • ENUM

    text,并带有限定其值的 CHECK

    带检查约束的文本保留相同的值,而且比 PostgreSQL 的 enum 类型更容易修改。

  • ENUM holding '' (MySQL's error value)

    检查约束允许的空值

    在宽松的 SQL 模式下,无效值写入 ENUM 时 MySQL 会存储空字符串。这些行会保留,检查约束允许它们。

    报告记录: enum_invalid_value

  • ENUM or SET holding values outside its list

    不加 CHECK

    列表中没有的值会使检查失败,因此不创建检查约束。

    报告记录: enum_check_skipped

  • SET

    text[],并带有每个元素都在列表中的 CHECK

    数组让每个元素保持独立,因此查询可以检验成员关系。

  • JSON

    jsonb;值包含 \u0000 时为 json

    jsonb 是 PostgreSQL 能良好索引和查询的类型,但它不接受 \u0000 转义,因此包含它的列保持为 json。

    报告记录: json_not_jsonb

  • NUL bytes in text

    删除

    PostgreSQL 文本不能包含 NUL 字节。该字节会被删除,报告会统计受影响的值。

    报告记录: nul_removed

  • text that isn't valid in its character set

    错误的字节变为替换字符

    PostgreSQL 文本必须是有效的 UTF-8。

    报告记录: invalid_utf8_replaced

  • text decoded twice (ñ)

    按原样复制

    转储中存储的正是这些字符,因此按原样复制;报告会指出它们。

    报告记录: mojibake_suspected

  • AUTO_INCREMENT

    GENERATED BY DEFAULT AS IDENTITY,计数器设在已复制的 id 之后

    标识列接受复制过来的 id,计数器从最大的 id 或转储中的 AUTO_INCREMENT 之后继续,取两者中较大者。

  • MyISAM AUTO_INCREMENT on the second column of a key

    整个表一个计数器

    MyISAM 按键的第一列分组计数;PostgreSQL 每列只有一个计数器。

    报告记录: myisam_group_autoincrement

  • ON UPDATE CURRENT_TIMESTAMP

    一个 BEFORE UPDATE 触发器

    当行发生变化而更新语句没有设置该列时,触发器将其设为当前时间,与 MySQL 相同。

    报告记录: on_update_trigger

  • generated columns

    STORED

    PostgreSQL 13 到 17 只支持存储的生成列;公式相同。

    报告记录: virtual_as_stored

  • generated columns with functions PostgreSQL lacks

    普通列

    无法为 PostgreSQL 写出的公式会留下一个普通列和一条报告记录。

    报告记录: generated_not_translated

  • INVISIBLE columns

    普通列

    PostgreSQL 没有不可见列。

    报告记录: invisible_column

  • CHECK constraints

    已转换,内联在 CREATE TABLE 中

    内联的检查约束在每行导入时检验,而不是事后对每个约束各扫描一遍。

  • CHECK constraints with functions PostgreSQL lacks

    不创建

    无法为 PostgreSQL 写出的检查约束会被省略,并在报告中列出。

    报告记录: check_skipped

  • CHECK ... NOT ENFORCED

    不创建

    MySQL 保留这些检查约束但不执行,因此行可能违反它们。

    报告记录: check_not_enforced

  • defaults with functions PostgreSQL lacks

    无默认值

    无法为 PostgreSQL 写出的默认值会被省略,并在报告中列出。

    报告记录: default_not_translated

  • keys and indexes on a prefix, c(20)

    基于 left(c, 20) 的索引

    PostgreSQL 可以为表达式建立索引,因此对相同的前缀建立索引。

    报告记录: prefix_unique

  • FULLTEXT indexes

    基于 to_tsvector('simple', ...) 的 GIN 索引

    全文搜索使用 @@ 代替 MATCH ... AGAINST。

    报告记录: fulltext_as_gin

  • indexes on values over 2,000 bytes

    hash

    btree 条目无法容纳这么长的值;hash 索引仍能找到相等的值。

    报告记录: index_on_wide_values

  • SPATIAL indexes

    没有 PostGIS 时不创建

    空间索引需要 PostGIS。

    报告记录: spatial_index_skipped

  • functional indexes PostgreSQL can't write

    不创建

    基于无法为 PostgreSQL 写出的表达式的索引会被省略,并在报告中列出。

    报告记录: index_skipped

  • foreign keys

    在数据之后以 NOT VALID 添加,然后验证,有孤立行时给出警告

    转储以 FOREIGN_KEY_CHECKS=0 导入,而 MyISAM 从不检查,因此没有父行的行很常见。外键先创建、最后验证;有孤立行时外键保持 NOT VALID 并给出警告,而不是中止导入。

  • foreign keys to columns without a unique key

    不创建

    PostgreSQL 要求被引用的列上有唯一键;InnoDB 不要求。

    报告记录: foreign_key_skipped

  • table and column names

    小写,前提是仍然唯一

    小写名称在 PostgreSQL 中可以不加引号书写。

    报告记录: identifier_renamed

  • names over 63 bytes

    用哈希值缩短

    PostgreSQL 名称最多 63 字节;哈希值让缩短后的名称互不相同。

    报告记录: name_shortened

  • GEOMETRY, POINT, POLYGON...

    WKB,存于 bytea

    没有 PostGIS 时,标准的 well-known binary 格式能精确保留每个几何对象。

    报告记录: geometry_as_wkb

  • INET4, INET6, UUID (MariaDB)

    inet, uuid

    PostgreSQL 有相同的类型。

  • BINARY, VARBINARY, BLOB

    bytea

    字节仍然是字节。

  • PARTITION BY

    单个表

    行保持不变;如有需要,请在 PostgreSQL 中重新对该表分区。

    报告记录: partitions_dropped

  • WITH SYSTEM VERSIONING (MariaDB)

    只含当前行的表

    转储只包含当前的行。

    报告记录: system_versioning_dropped

  • MyISAM, MEMORY and other engines

    普通表

    PostgreSQL 只有一种表引擎。

    报告记录: engine_dropped

  • views, triggers, procedures, functions and events

    在报告中列出

    它们的代码用 MySQL 的语言编写,因此会列出来,由你重新创建。

    报告记录: not_converted_objects

  • dumps without CREATE TABLE

    根据值推断类型

    使用 --no-create-info 生成的转储只有行,因此类型来自值。

    报告记录: no_schema

  • geometries in a dump without CREATE TABLE

    WKB,存于 bytea

    MySQL 的几何格式根据值识别,并存储为标准 WKB。

    报告记录: geometry_inferred

  • _ci collations

    精确比较

    除非另有指定,PostgreSQL 会精确比较文本。

    报告记录: case_insensitive_collation

  • SET NAMES utf8 from utf8mb4 tables

    已转换,并给出警告

    MySQL 5.x 的 mysqldump 默认写出 3 字节 utf8,在任何转换器看到文件之前就已把每个 emoji 替换为问号。

    报告记录: dump_lost_four_byte_chars

  • dumps without SET NAMES whose text isn't UTF-8

    按 Latin-1 (Windows-1252) 读取

    MySQL 的 latin1 就是 Windows-1252,这是此类转储常用的编码。

    报告记录: charset_guessed

  • sjis, cp932, gbk, big5 dumps

    按 UTF-8 读取,并给出警告

    这些编码暂时无法解码。

    报告记录: unsupported_charset

  • UNHEX('..'), CONVERT(b'..', UNSIGNED), CONVERT(".." USING x), ST_GeomFromText('..') in rows

    求值

    有些转储工具把值写成这些函数调用;它们按其产生的值读取,地理 WKT 会转为经度在前。

  • 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

    损坏或手写的转储可能包含列无法容纳的值;这些值变为 NULL,以便其余数据能够导入。

    报告记录: invalid_value_null

  • unreadable geometries

    NULL

    不是几何数据的字节无法作为几何数据存储。

    报告记录: bad_geometry

  • CREATE SEQUENCE (MariaDB)

    CREATE SEQUENCE,带当前值

    PostgreSQL 序列接受相同的选项。

在 MySQL 中在 PostgreSQL 中报告记录
TINYINT(1), BOOL所有值都是 0 或 1 时为 boolean,否则为 smallintMySQL 的 BOOL 是 TINYINT(1),可以存储 -128 到 127 之间的任何数字。只包含 0 和 1 的列变为 boolean;包含 2 或 -1 的列保持为数字,因此没有值会改变含义。bool_kept_numeric
UNSIGNED integers更宽一级的整数,并带有不能为负数的 CHECK;所有值都能容纳时 BIGINT UNSIGNED 为 bigint,否则为 numeric(20,0)PostgreSQL 没有无符号整数。更宽的类型能容纳整个无符号范围,检查约束像 MySQL 一样阻止负值。unsigned_widened
ZEROFILL and display widths去除补零只是数字的显示方式,不是数字的一部分。数字会保留;如果需要补零,请在读取时格式化。zerofill_dropped
FLOAT(M,D)real 或 double precision,不带小数位数PostgreSQL 的浮点数没有固定的小数位数。已存储的值按原样复制。float_scale_dropped
BIT(1), BIT(n)boolean, bit(n)单个位表示是或否;更宽的位字段保留每一位。
zero, partial or impossible dates (0000-00-00, 2024-02-30)NULLPostgreSQL 只存储真实的日历日期。当 SQL 模式允许时,MySQL 会保留零日期和不可能的日期;它们会变为 NULL,报告会统计其数量。invalid_date_null
NOT NULL columns with values that become NULL去除 NOT NULL值必须变为 NULL 的列无法保留 NOT NULL,否则导入会中止。not_null_relaxed
DEFAULT '0000-00-00'无默认值PostgreSQL 无法存储的默认值会被省略,而不是替换为编造的日期。zero_date_default
TIME所有值都在一天以内时为 time,否则为 intervalMySQL 的 TIME 是从 -838:59:59 到 838:59:59 的时长。值都在一天以内的列保持为一天中的时刻;有更长或负值的列变为 interval。time_as_interval
YEARsmallint,并带有限定范围的 CHECKPostgreSQL 没有年份类型;检查约束只允许 MySQL 允许的那些值。
CHAR(n)varchar(n)MySQL 读取 CHAR 值时会去掉末尾空格,而 PostgreSQL 的 char(n) 会把它们加回来,因此用 varchar(n) 按 MySQL 返回的样子保留值。
DATETIMEtimestampDATETIME 是不带时区的挂钟读数,与 PostgreSQL 的 timestamp 相同。
TIMESTAMP使用转储声明的时区的 timestamptz,未声明时为 UTCTIMESTAMP 是一个时间点。转储会声明写入时使用的时区(mysqldump 写入 UTC);没有声明时,值按 UTC 读取,报告会说明这一点。timestamp_zone_assumed
ENUMtext,并带有限定其值的 CHECK带检查约束的文本保留相同的值,而且比 PostgreSQL 的 enum 类型更容易修改。
ENUM holding '' (MySQL's error value)检查约束允许的空值在宽松的 SQL 模式下,无效值写入 ENUM 时 MySQL 会存储空字符串。这些行会保留,检查约束允许它们。enum_invalid_value
ENUM or SET holding values outside its list不加 CHECK列表中没有的值会使检查失败,因此不创建检查约束。enum_check_skipped
SETtext[],并带有每个元素都在列表中的 CHECK数组让每个元素保持独立,因此查询可以检验成员关系。
JSONjsonb;值包含 \u0000 时为 jsonjsonb 是 PostgreSQL 能良好索引和查询的类型,但它不接受 \u0000 转义,因此包含它的列保持为 json。json_not_jsonb
NUL bytes in text删除PostgreSQL 文本不能包含 NUL 字节。该字节会被删除,报告会统计受影响的值。nul_removed
text that isn't valid in its character set错误的字节变为替换字符PostgreSQL 文本必须是有效的 UTF-8。invalid_utf8_replaced
text decoded twice (ñ)按原样复制转储中存储的正是这些字符,因此按原样复制;报告会指出它们。mojibake_suspected
AUTO_INCREMENTGENERATED BY DEFAULT AS IDENTITY,计数器设在已复制的 id 之后标识列接受复制过来的 id,计数器从最大的 id 或转储中的 AUTO_INCREMENT 之后继续,取两者中较大者。
MyISAM AUTO_INCREMENT on the second column of a key整个表一个计数器MyISAM 按键的第一列分组计数;PostgreSQL 每列只有一个计数器。myisam_group_autoincrement
ON UPDATE CURRENT_TIMESTAMP一个 BEFORE UPDATE 触发器当行发生变化而更新语句没有设置该列时,触发器将其设为当前时间,与 MySQL 相同。on_update_trigger
generated columnsSTOREDPostgreSQL 13 到 17 只支持存储的生成列;公式相同。virtual_as_stored
generated columns with functions PostgreSQL lacks普通列无法为 PostgreSQL 写出的公式会留下一个普通列和一条报告记录。generated_not_translated
INVISIBLE columns普通列PostgreSQL 没有不可见列。invisible_column
CHECK constraints已转换,内联在 CREATE TABLE 中内联的检查约束在每行导入时检验,而不是事后对每个约束各扫描一遍。
CHECK constraints with functions PostgreSQL lacks不创建无法为 PostgreSQL 写出的检查约束会被省略,并在报告中列出。check_skipped
CHECK ... NOT ENFORCED不创建MySQL 保留这些检查约束但不执行,因此行可能违反它们。check_not_enforced
defaults with functions PostgreSQL lacks无默认值无法为 PostgreSQL 写出的默认值会被省略,并在报告中列出。default_not_translated
keys and indexes on a prefix, c(20)基于 left(c, 20) 的索引PostgreSQL 可以为表达式建立索引,因此对相同的前缀建立索引。prefix_unique
FULLTEXT indexes基于 to_tsvector('simple', ...) 的 GIN 索引全文搜索使用 @@ 代替 MATCH ... AGAINST。fulltext_as_gin
indexes on values over 2,000 byteshashbtree 条目无法容纳这么长的值;hash 索引仍能找到相等的值。index_on_wide_values
SPATIAL indexes没有 PostGIS 时不创建空间索引需要 PostGIS。spatial_index_skipped
functional indexes PostgreSQL can't write不创建基于无法为 PostgreSQL 写出的表达式的索引会被省略,并在报告中列出。index_skipped
foreign keys在数据之后以 NOT VALID 添加,然后验证,有孤立行时给出警告转储以 FOREIGN_KEY_CHECKS=0 导入,而 MyISAM 从不检查,因此没有父行的行很常见。外键先创建、最后验证;有孤立行时外键保持 NOT VALID 并给出警告,而不是中止导入。
foreign keys to columns without a unique key不创建PostgreSQL 要求被引用的列上有唯一键;InnoDB 不要求。foreign_key_skipped
table and column names小写,前提是仍然唯一小写名称在 PostgreSQL 中可以不加引号书写。identifier_renamed
names over 63 bytes用哈希值缩短PostgreSQL 名称最多 63 字节;哈希值让缩短后的名称互不相同。name_shortened
GEOMETRY, POINT, POLYGON...WKB,存于 bytea没有 PostGIS 时,标准的 well-known binary 格式能精确保留每个几何对象。geometry_as_wkb
INET4, INET6, UUID (MariaDB)inet, uuidPostgreSQL 有相同的类型。
BINARY, VARBINARY, BLOBbytea字节仍然是字节。
PARTITION BY单个表行保持不变;如有需要,请在 PostgreSQL 中重新对该表分区。partitions_dropped
WITH SYSTEM VERSIONING (MariaDB)只含当前行的表转储只包含当前的行。system_versioning_dropped
MyISAM, MEMORY and other engines普通表PostgreSQL 只有一种表引擎。engine_dropped
views, triggers, procedures, functions and events在报告中列出它们的代码用 MySQL 的语言编写,因此会列出来,由你重新创建。not_converted_objects
dumps without CREATE TABLE根据值推断类型使用 --no-create-info 生成的转储只有行,因此类型来自值。no_schema
geometries in a dump without CREATE TABLEWKB,存于 byteaMySQL 的几何格式根据值识别,并存储为标准 WKB。geometry_inferred
_ci collations精确比较除非另有指定,PostgreSQL 会精确比较文本。case_insensitive_collation
SET NAMES utf8 from utf8mb4 tables已转换,并给出警告MySQL 5.x 的 mysqldump 默认写出 3 字节 utf8,在任何转换器看到文件之前就已把每个 emoji 替换为问号。dump_lost_four_byte_chars
dumps without SET NAMES whose text isn't UTF-8按 Latin-1 (Windows-1252) 读取MySQL 的 latin1 就是 Windows-1252,这是此类转储常用的编码。charset_guessed
sjis, cp932, gbk, big5 dumps按 UTF-8 读取,并给出警告这些编码暂时无法解码。unsupported_charset
UNHEX('..'), CONVERT(b'..', UNSIGNED), CONVERT(".." USING x), ST_GeomFromText('..') in rows求值有些转储工具把值写成这些函数调用;它们按其产生的值读取,地理 WKT 会转为经度在前。
other expressions in rowsNULL不运行数据库的转换器无法对它们求值。expression_value
DEFAULT in VALUES该列的默认值字面量默认值会复制到该行中。default_keyword_filled
DEFAULT in VALUES without a literal defaultNULL没有可复制的值。default_keyword_null
REPLACE and INSERT IGNORE普通行转储中每个键只有一行,因此行是相同的。replace_as_insert
types IntoSQL doesn't knowtext文本可以容纳任何值;请在导入后更改类型。unknown_type
values that don't fit their columnNULL损坏或手写的转储可能包含列无法容纳的值;这些值变为 NULL,以便其余数据能够导入。invalid_value_null
unreadable geometriesNULL不是几何数据的字节无法作为几何数据存储。bad_geometry
CREATE SEQUENCE (MariaDB)CREATE SEQUENCE,带当前值PostgreSQL 序列接受相同的选项。

将脚本导入 PostgreSQL

脚本先创建表、复制行,再添加键、索引和外键,因此导入速度快,并会在遇到第一个错误时停止。

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。同一个脚本在每个版本上都能导入,如有问题会在第一个错误处停止。