Converter MySQL para PostgreSQL
Solte um dump do mysqldump, mariadb-dump, mysqlpump, phpMyAdmin ou Adminer, em .sql ou .sql.gz, e baixe um script do PostgreSQL com as mesmas tabelas, chaves, índices e dados. Cada valor que o PostgreSQL não aceita como está, e cada objeto que ele não traz, aparece no relatório.
18 linhas convertidas, 8 correções, nenhuma rejeitada
Tabelas em shop_2024.sqlDump do MySQL 8.0.46 · gerado por mysqldump · utf8mb4
customers8 linhas3
7 colunas id bigint not null name varchar(100) not null email varchar(150) not null country varchar(2) not null is_active boolean not null signed_up date created_at timestamptz(0) not null orders10 linhas5
9 colunas id bigint not null customer_id bigint not null status text not null total numeric(10,2) not null items integer not null ordered_at timestamp(0) not null shipped_at timestamp(0) note varchar(255) updated_at timestamptz(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();
Correções
- "id" de customers é sem sinal, então virou bigint com um CHECK de que não é negativa.
- "signed_up" de customers agora aceita NULL: alguns dos seus valores não puderam ser guardados e são NULL.
- O valor padrão de "signed_up" em customers, 0000‑00‑00, não é uma data que o PostgreSQL aceite, então a coluna fica sem valor padrão.
- "customer_id" de orders é sem sinal, então virou bigint com um CHECK de que não é negativa.
- "id" de orders é sem sinal, então virou bigint com um CHECK de que não é negativa.
- "items" de orders é sem sinal, então virou integer com um CHECK de que não é negativa.
- 1 linha de "status" em orders contém o valor vazio que o MySQL guarda para entradas inválidas; o CHECK dos valores o permite.
- "updated_at" de orders muda para a hora atual a cada atualização; no PostgreSQL, um gatilho faz isso.
Para revisar
- 1 valor em "signed_up" de customers é uma data zero ou impossível, como 0000‑00‑00, que o PostgreSQL não consegue guardar, então ficou NULL. Se essas datas têm significado, troque-as na origem por datas reais antes do dump.
- 1 valor em "shipped_at" de orders é uma data zero ou impossível, como 0000‑00‑00 00:00:00, que o PostgreSQL não consegue guardar, então ficou NULL. Se essas datas têm significado, troque-as na origem por datas reais antes do dump.
Seu arquivo é convertido neste computador e nunca é enviado.
Outros exemplos:
O que muda do MySQL para o PostgreSQL
Cada linha é uma regra que o conversor aplica, com a linha do relatório que ele escreve quando a regra muda algo nos seus dados.
TINYINT(1), BOOL
boolean quando todo valor é 0 ou 1, senão smallint
O BOOL do MySQL é um TINYINT(1) que aceita qualquer número de -128 a 127. Uma coluna que só contém 0 e 1 vira boolean; uma que contém 2 ou -1 continua sendo número, para que nenhum valor mude de significado.
Linha do relatório: bool_kept_numeric
UNSIGNED integers
o próximo inteiro mais largo com um CHECK de que não é negativo; BIGINT UNSIGNED vira bigint quando todo valor cabe, senão numeric(20,0)
O PostgreSQL não tem inteiros sem sinal. Um tipo mais largo comporta todo o intervalo sem sinal, e o CHECK impede valores negativos, como o MySQL fazia.
Linha do relatório: unsigned_widened
ZEROFILL and display widths
descartado
O preenchimento com zeros é uma forma de exibir um número, não parte dele. Os números são mantidos; formate-os na leitura se o preenchimento for importante.
Linha do relatório: zerofill_dropped
FLOAT(M,D)
real ou double precision, sem a escala
Os floats do PostgreSQL não têm escala fixa. Os valores guardados são copiados como estão.
Linha do relatório: float_scale_dropped
BIT(1), BIT(n)
boolean, bit(n)
Um único bit é um sim ou não; campos de bits mais largos mantêm todos os bits.
zero, partial or impossible dates (0000-00-00, 2024-02-30)
NULL
O PostgreSQL só guarda datas reais do calendário. O MySQL mantém datas zero e impossíveis quando o modo SQL permite; elas viram NULL e o relatório as conta.
Linha do relatório: invalid_date_null
NOT NULL columns with values that become NULL
NOT NULL removido
Uma coluna cujos valores tiveram de virar NULL não pode manter NOT NULL, senão a carga pararia.
Linha do relatório: not_null_relaxed
DEFAULT '0000-00-00'
sem valor padrão
Um valor padrão que o PostgreSQL não consegue guardar fica de fora em vez de ser trocado por uma data inventada.
Linha do relatório: zero_date_default
TIME
time quando todo valor cabe em um dia, senão interval
O TIME do MySQL é uma duração de -838:59:59 a 838:59:59. Uma coluna cujos valores cabem em um dia continua sendo uma hora do dia; uma com valores maiores ou negativos vira interval.
Linha do relatório: time_as_interval
YEAR
smallint com um CHECK do intervalo
O PostgreSQL não tem tipo de ano; o CHECK mantém os mesmos valores que o MySQL permite.
CHAR(n)
varchar(n)
O MySQL remove os espaços finais dos valores CHAR ao lê-los, e o char(n) do PostgreSQL os acrescentaria de volta, então varchar(n) mantém os valores como o MySQL os retorna.
DATETIME
timestamp
Um DATETIME é a leitura de um relógio de parede sem fuso horário, como o timestamp do PostgreSQL.
TIMESTAMP
timestamptz no fuso horário que o dump informa, UTC quando ele não informa nenhum
Um TIMESTAMP é um instante. Os dumps informam o fuso em que foram escritos (o mysqldump escreve em UTC); sem isso, os valores são lidos como UTC e o relatório avisa.
Linha do relatório: timestamp_zone_assumed
ENUM
text com um CHECK dos valores
Texto com CHECK mantém os mesmos valores e é mais fácil de mudar do que um enum type do PostgreSQL.
ENUM holding '' (MySQL's error value)
o valor vazio, permitido pelo CHECK
O MySQL guarda uma string vazia quando um valor inválido entra em um ENUM em um modo SQL permissivo. Essas linhas são mantidas e o CHECK as permite.
Linha do relatório: enum_invalid_value
ENUM or SET holding values outside its list
sem CHECK
Valores que a lista não nomeia falhariam no CHECK, então o CHECK não é criado.
Linha do relatório: enum_check_skipped
SET
text[] com um CHECK de que cada elemento está na lista
Um array mantém cada elemento separado, então as consultas podem testar se um valor pertence a ele.
JSON
jsonb; json quando um valor contém \u0000
jsonb é o tipo que o PostgreSQL indexa e consulta bem, mas ele rejeita o escape \u0000, então uma coluna com ele continua json.
Linha do relatório: json_not_jsonb
NUL bytes in text
removido
O texto do PostgreSQL não aceita o byte NUL. O byte é removido e o relatório conta os valores.
Linha do relatório: nul_removed
text that isn't valid in its character set
os bytes inválidos como o caractere de substituição
O texto do PostgreSQL deve ser UTF-8 válido.
Linha do relatório: invalid_utf8_replaced
text decoded twice (ñ)
copiado como está
O dump contém exatamente esses caracteres, então eles são copiados; o relatório os aponta.
Linha do relatório: mojibake_suspected
AUTO_INCREMENT
GENERATED BY DEFAULT AS IDENTITY, com o contador ajustado depois dos ids copiados
Colunas identity aceitam os ids copiados, e o contador continua depois do maior deles ou do AUTO_INCREMENT do dump, o que for maior.
MyISAM AUTO_INCREMENT on the second column of a key
um contador para a tabela
O MyISAM conta por grupo da primeira coluna da chave; o PostgreSQL tem um contador por coluna.
Linha do relatório: myisam_group_autoincrement
ON UPDATE CURRENT_TIMESTAMP
um gatilho BEFORE UPDATE
O gatilho define a coluna com a hora atual quando uma linha muda e a atualização não a definiu, como o MySQL faz.
Linha do relatório: on_update_trigger
generated columns
STORED
O PostgreSQL 13 a 17 só armazena colunas geradas; a fórmula é a mesma.
Linha do relatório: virtual_as_stored
generated columns with functions PostgreSQL lacks
uma coluna simples
Uma fórmula que não pode ser escrita para o PostgreSQL deixa uma coluna simples e uma linha no relatório.
Linha do relatório: generated_not_translated
INVISIBLE columns
colunas comuns
O PostgreSQL não tem colunas invisíveis.
Linha do relatório: invisible_column
CHECK constraints
traduzido, dentro do CREATE TABLE
Os CHECKs dentro do CREATE TABLE são testados à medida que cada linha carrega, em vez de uma varredura por CHECK depois.
CHECK constraints with functions PostgreSQL lacks
não criado
Um CHECK que não pode ser escrito para o PostgreSQL fica de fora e é informado no relatório.
Linha do relatório: check_skipped
CHECK ... NOT ENFORCED
não criado
O MySQL mantém esses CHECKs sem aplicá-los, então as linhas podem violá-los.
Linha do relatório: check_not_enforced
defaults with functions PostgreSQL lacks
sem valor padrão
Um valor padrão que não pode ser escrito para o PostgreSQL fica de fora e é informado no relatório.
Linha do relatório: default_not_translated
keys and indexes on a prefix, c(20)
um índice sobre left(c, 20)
O PostgreSQL indexa expressões, então o mesmo prefixo é indexado.
Linha do relatório: prefix_unique
FULLTEXT indexes
GIN sobre to_tsvector('simple', ...)
A busca de texto completo funciona com @@ em vez de MATCH ... AGAINST.
Linha do relatório: fulltext_as_gin
indexes on values over 2,000 bytes
hash
Uma entrada de btree não comporta valores tão longos; um índice hash ainda encontra valores iguais.
Linha do relatório: index_on_wide_values
SPATIAL indexes
não criado sem PostGIS
Índices espaciais precisam do PostGIS.
Linha do relatório: spatial_index_skipped
functional indexes PostgreSQL can't write
não criado
Um índice sobre uma expressão que não pode ser escrita para o PostgreSQL fica de fora e é informado no relatório.
Linha do relatório: index_skipped
foreign keys
depois dos dados, NOT VALID, e então validada com um aviso sobre órfãos
Os dumps são carregados com FOREIGN_KEY_CHECKS=0 e o MyISAM nunca verifica, então linhas sem pai são comuns. A chave é criada primeiro e validada por último; órfãos a deixam NOT VALID com um aviso, em vez de parar a carga.
foreign keys to columns without a unique key
não criada
O PostgreSQL exige uma chave única nas colunas referenciadas; o InnoDB não.
Linha do relatório: foreign_key_skipped
table and column names
em minúsculas quando continua único
Nomes em minúsculas podem ser escritos sem aspas no PostgreSQL.
Linha do relatório: identifier_renamed
names over 63 bytes
encurtado com um hash
Os nomes do PostgreSQL têm até 63 bytes; o hash mantém os nomes encurtados distintos.
Linha do relatório: name_shortened
GEOMETRY, POINT, POLYGON...
WKB em bytea
Sem PostGIS, o formato binário padrão WKB (well-known binary) mantém cada geometria exata.
Linha do relatório: geometry_as_wkb
INET4, INET6, UUID (MariaDB)
inet, uuid
O PostgreSQL tem os mesmos tipos.
BINARY, VARBINARY, BLOB
bytea
Bytes continuam sendo bytes.
PARTITION BY
uma tabela
As linhas são as mesmas; particione a tabela de novo no PostgreSQL se precisar.
Linha do relatório: partitions_dropped
WITH SYSTEM VERSIONING (MariaDB)
uma tabela com as linhas atuais
Um dump contém só as linhas atuais.
Linha do relatório: system_versioning_dropped
MyISAM, MEMORY and other engines
uma tabela comum
O PostgreSQL tem um único mecanismo de tabelas.
Linha do relatório: engine_dropped
views, triggers, procedures, functions and events
listados no relatório
O código deles é escrito na linguagem do MySQL, então eles são listados para você recriá-los.
Linha do relatório: not_converted_objects
dumps without CREATE TABLE
tipos deduzidos dos valores
Um dump feito com --no-create-info só tem linhas, então os tipos vêm dos valores.
Linha do relatório: no_schema
geometries in a dump without CREATE TABLE
WKB em bytea
O formato de geometria do MySQL é reconhecido pelos valores e guardado como WKB padrão.
Linha do relatório: geometry_inferred
_ci collations
comparação exata
O PostgreSQL compara texto de forma exata, a menos que se indique o contrário.
Linha do relatório: case_insensitive_collation
SET NAMES utf8 from utf8mb4 tables
convertido, com um aviso
O mysqldump do MySQL 5.x escreve utf8 de 3 bytes por padrão e troca cada emoji por um ponto de interrogação antes que qualquer conversor veja o arquivo.
Linha do relatório: dump_lost_four_byte_chars
dumps without SET NAMES whose text isn't UTF-8
lido como Latin-1 (Windows-1252)
O latin1 do MySQL é o Windows-1252, a codificação comum desses dumps.
Linha do relatório: charset_guessed
sjis, cp932, gbk, big5 dumps
lido como UTF-8, com um aviso
Essas codificações ainda não são decodificadas.
Linha do relatório: unsupported_charset
UNHEX('..'), CONVERT(b'..', UNSIGNED), CONVERT(".." USING x), ST_GeomFromText('..') in rows
avaliadas
Algumas ferramentas de dump escrevem valores como essas chamadas; elas são lidas como os valores que produzem, com o WKT geográfico convertido para longitude primeiro.
other expressions in rows
NULL
Um conversor que não executa um banco de dados não consegue avaliá-las.
Linha do relatório: expression_value
DEFAULT in VALUES
o valor padrão da coluna
Um valor padrão literal é copiado para a linha.
Linha do relatório: default_keyword_filled
DEFAULT in VALUES without a literal default
NULL
Não há valor para copiar.
Linha do relatório: default_keyword_null
REPLACE and INSERT IGNORE
linhas simples
Um dump tem uma linha por chave, então as linhas são as mesmas.
Linha do relatório: replace_as_insert
types IntoSQL doesn't know
text
O tipo text aceita qualquer valor; mude o tipo depois da carga.
Linha do relatório: unknown_type
values that don't fit their column
NULL
Um dump danificado ou escrito à mão pode conter valores que a coluna não aceita; eles viram NULL para que o resto carregue.
Linha do relatório: invalid_value_null
unreadable geometries
NULL
Bytes que não são uma geometria não podem ser guardados como uma.
Linha do relatório: bad_geometry
CREATE SEQUENCE (MariaDB)
CREATE SEQUENCE com o valor atual
As sequências do PostgreSQL aceitam as mesmas opções.
| No MySQL | No PostgreSQL | Linha do relatório |
|---|---|---|
| TINYINT(1), BOOL | boolean quando todo valor é 0 ou 1, senão smallintO BOOL do MySQL é um TINYINT(1) que aceita qualquer número de -128 a 127. Uma coluna que só contém 0 e 1 vira boolean; uma que contém 2 ou -1 continua sendo número, para que nenhum valor mude de significado. | bool_kept_numeric |
| UNSIGNED integers | o próximo inteiro mais largo com um CHECK de que não é negativo; BIGINT UNSIGNED vira bigint quando todo valor cabe, senão numeric(20,0)O PostgreSQL não tem inteiros sem sinal. Um tipo mais largo comporta todo o intervalo sem sinal, e o CHECK impede valores negativos, como o MySQL fazia. | unsigned_widened |
| ZEROFILL and display widths | descartadoO preenchimento com zeros é uma forma de exibir um número, não parte dele. Os números são mantidos; formate-os na leitura se o preenchimento for importante. | zerofill_dropped |
| FLOAT(M,D) | real ou double precision, sem a escalaOs floats do PostgreSQL não têm escala fixa. Os valores guardados são copiados como estão. | float_scale_dropped |
| BIT(1), BIT(n) | boolean, bit(n)Um único bit é um sim ou não; campos de bits mais largos mantêm todos os bits. | |
| zero, partial or impossible dates (0000-00-00, 2024-02-30) | NULLO PostgreSQL só guarda datas reais do calendário. O MySQL mantém datas zero e impossíveis quando o modo SQL permite; elas viram NULL e o relatório as conta. | invalid_date_null |
| NOT NULL columns with values that become NULL | NOT NULL removidoUma coluna cujos valores tiveram de virar NULL não pode manter NOT NULL, senão a carga pararia. | not_null_relaxed |
| DEFAULT '0000-00-00' | sem valor padrãoUm valor padrão que o PostgreSQL não consegue guardar fica de fora em vez de ser trocado por uma data inventada. | zero_date_default |
| TIME | time quando todo valor cabe em um dia, senão intervalO TIME do MySQL é uma duração de -838:59:59 a 838:59:59. Uma coluna cujos valores cabem em um dia continua sendo uma hora do dia; uma com valores maiores ou negativos vira interval. | time_as_interval |
| YEAR | smallint com um CHECK do intervaloO PostgreSQL não tem tipo de ano; o CHECK mantém os mesmos valores que o MySQL permite. | |
| CHAR(n) | varchar(n)O MySQL remove os espaços finais dos valores CHAR ao lê-los, e o char(n) do PostgreSQL os acrescentaria de volta, então varchar(n) mantém os valores como o MySQL os retorna. | |
| DATETIME | timestampUm DATETIME é a leitura de um relógio de parede sem fuso horário, como o timestamp do PostgreSQL. | |
| TIMESTAMP | timestamptz no fuso horário que o dump informa, UTC quando ele não informa nenhumUm TIMESTAMP é um instante. Os dumps informam o fuso em que foram escritos (o mysqldump escreve em UTC); sem isso, os valores são lidos como UTC e o relatório avisa. | timestamp_zone_assumed |
| ENUM | text com um CHECK dos valoresTexto com CHECK mantém os mesmos valores e é mais fácil de mudar do que um enum type do PostgreSQL. | |
| ENUM holding '' (MySQL's error value) | o valor vazio, permitido pelo CHECKO MySQL guarda uma string vazia quando um valor inválido entra em um ENUM em um modo SQL permissivo. Essas linhas são mantidas e o CHECK as permite. | enum_invalid_value |
| ENUM or SET holding values outside its list | sem CHECKValores que a lista não nomeia falhariam no CHECK, então o CHECK não é criado. | enum_check_skipped |
| SET | text[] com um CHECK de que cada elemento está na listaUm array mantém cada elemento separado, então as consultas podem testar se um valor pertence a ele. | |
| JSON | jsonb; json quando um valor contém \u0000jsonb é o tipo que o PostgreSQL indexa e consulta bem, mas ele rejeita o escape \u0000, então uma coluna com ele continua json. | json_not_jsonb |
| NUL bytes in text | removidoO texto do PostgreSQL não aceita o byte NUL. O byte é removido e o relatório conta os valores. | nul_removed |
| text that isn't valid in its character set | os bytes inválidos como o caractere de substituiçãoO texto do PostgreSQL deve ser UTF-8 válido. | invalid_utf8_replaced |
| text decoded twice (ñ) | copiado como estáO dump contém exatamente esses caracteres, então eles são copiados; o relatório os aponta. | mojibake_suspected |
| AUTO_INCREMENT | GENERATED BY DEFAULT AS IDENTITY, com o contador ajustado depois dos ids copiadosColunas identity aceitam os ids copiados, e o contador continua depois do maior deles ou do AUTO_INCREMENT do dump, o que for maior. | |
| MyISAM AUTO_INCREMENT on the second column of a key | um contador para a tabelaO MyISAM conta por grupo da primeira coluna da chave; o PostgreSQL tem um contador por coluna. | myisam_group_autoincrement |
| ON UPDATE CURRENT_TIMESTAMP | um gatilho BEFORE UPDATEO gatilho define a coluna com a hora atual quando uma linha muda e a atualização não a definiu, como o MySQL faz. | on_update_trigger |
| generated columns | STOREDO PostgreSQL 13 a 17 só armazena colunas geradas; a fórmula é a mesma. | virtual_as_stored |
| generated columns with functions PostgreSQL lacks | uma coluna simplesUma fórmula que não pode ser escrita para o PostgreSQL deixa uma coluna simples e uma linha no relatório. | generated_not_translated |
| INVISIBLE columns | colunas comunsO PostgreSQL não tem colunas invisíveis. | invisible_column |
| CHECK constraints | traduzido, dentro do CREATE TABLEOs CHECKs dentro do CREATE TABLE são testados à medida que cada linha carrega, em vez de uma varredura por CHECK depois. | |
| CHECK constraints with functions PostgreSQL lacks | não criadoUm CHECK que não pode ser escrito para o PostgreSQL fica de fora e é informado no relatório. | check_skipped |
| CHECK ... NOT ENFORCED | não criadoO MySQL mantém esses CHECKs sem aplicá-los, então as linhas podem violá-los. | check_not_enforced |
| defaults with functions PostgreSQL lacks | sem valor padrãoUm valor padrão que não pode ser escrito para o PostgreSQL fica de fora e é informado no relatório. | default_not_translated |
| keys and indexes on a prefix, c(20) | um índice sobre left(c, 20)O PostgreSQL indexa expressões, então o mesmo prefixo é indexado. | prefix_unique |
| FULLTEXT indexes | GIN sobre to_tsvector('simple', ...)A busca de texto completo funciona com @@ em vez de MATCH ... AGAINST. | fulltext_as_gin |
| indexes on values over 2,000 bytes | hashUma entrada de btree não comporta valores tão longos; um índice hash ainda encontra valores iguais. | index_on_wide_values |
| SPATIAL indexes | não criado sem PostGISÍndices espaciais precisam do PostGIS. | spatial_index_skipped |
| functional indexes PostgreSQL can't write | não criadoUm índice sobre uma expressão que não pode ser escrita para o PostgreSQL fica de fora e é informado no relatório. | index_skipped |
| foreign keys | depois dos dados, NOT VALID, e então validada com um aviso sobre órfãosOs dumps são carregados com FOREIGN_KEY_CHECKS=0 e o MyISAM nunca verifica, então linhas sem pai são comuns. A chave é criada primeiro e validada por último; órfãos a deixam NOT VALID com um aviso, em vez de parar a carga. | |
| foreign keys to columns without a unique key | não criadaO PostgreSQL exige uma chave única nas colunas referenciadas; o InnoDB não. | foreign_key_skipped |
| table and column names | em minúsculas quando continua únicoNomes em minúsculas podem ser escritos sem aspas no PostgreSQL. | identifier_renamed |
| names over 63 bytes | encurtado com um hashOs nomes do PostgreSQL têm até 63 bytes; o hash mantém os nomes encurtados distintos. | name_shortened |
| GEOMETRY, POINT, POLYGON... | WKB em byteaSem PostGIS, o formato binário padrão WKB (well-known binary) mantém cada geometria exata. | geometry_as_wkb |
| INET4, INET6, UUID (MariaDB) | inet, uuidO PostgreSQL tem os mesmos tipos. | |
| BINARY, VARBINARY, BLOB | byteaBytes continuam sendo bytes. | |
| PARTITION BY | uma tabelaAs linhas são as mesmas; particione a tabela de novo no PostgreSQL se precisar. | partitions_dropped |
| WITH SYSTEM VERSIONING (MariaDB) | uma tabela com as linhas atuaisUm dump contém só as linhas atuais. | system_versioning_dropped |
| MyISAM, MEMORY and other engines | uma tabela comumO PostgreSQL tem um único mecanismo de tabelas. | engine_dropped |
| views, triggers, procedures, functions and events | listados no relatórioO código deles é escrito na linguagem do MySQL, então eles são listados para você recriá-los. | not_converted_objects |
| dumps without CREATE TABLE | tipos deduzidos dos valoresUm dump feito com --no-create-info só tem linhas, então os tipos vêm dos valores. | no_schema |
| geometries in a dump without CREATE TABLE | WKB em byteaO formato de geometria do MySQL é reconhecido pelos valores e guardado como WKB padrão. | geometry_inferred |
| _ci collations | comparação exataO PostgreSQL compara texto de forma exata, a menos que se indique o contrário. | case_insensitive_collation |
| SET NAMES utf8 from utf8mb4 tables | convertido, com um avisoO mysqldump do MySQL 5.x escreve utf8 de 3 bytes por padrão e troca cada emoji por um ponto de interrogação antes que qualquer conversor veja o arquivo. | dump_lost_four_byte_chars |
| dumps without SET NAMES whose text isn't UTF-8 | lido como Latin-1 (Windows-1252)O latin1 do MySQL é o Windows-1252, a codificação comum desses dumps. | charset_guessed |
| sjis, cp932, gbk, big5 dumps | lido como UTF-8, com um avisoEssas codificações ainda não são decodificadas. | unsupported_charset |
| UNHEX('..'), CONVERT(b'..', UNSIGNED), CONVERT(".." USING x), ST_GeomFromText('..') in rows | avaliadasAlgumas ferramentas de dump escrevem valores como essas chamadas; elas são lidas como os valores que produzem, com o WKT geográfico convertido para longitude primeiro. | |
| other expressions in rows | NULLUm conversor que não executa um banco de dados não consegue avaliá-las. | expression_value |
| DEFAULT in VALUES | o valor padrão da colunaUm valor padrão literal é copiado para a linha. | default_keyword_filled |
| DEFAULT in VALUES without a literal default | NULLNão há valor para copiar. | default_keyword_null |
| REPLACE and INSERT IGNORE | linhas simplesUm dump tem uma linha por chave, então as linhas são as mesmas. | replace_as_insert |
| types IntoSQL doesn't know | textO tipo text aceita qualquer valor; mude o tipo depois da carga. | unknown_type |
| values that don't fit their column | NULLUm dump danificado ou escrito à mão pode conter valores que a coluna não aceita; eles viram NULL para que o resto carregue. | invalid_value_null |
| unreadable geometries | NULLBytes que não são uma geometria não podem ser guardados como uma. | bad_geometry |
| CREATE SEQUENCE (MariaDB) | CREATE SEQUENCE com o valor atualAs sequências do PostgreSQL aceitam as mesmas opções. |
Carregue o script no PostgreSQL
O script cria as tabelas, copia as linhas e depois adiciona chaves, índices e chaves estrangeiras, então carrega rápido e para no primeiro erro.
psql -v ON_ERROR_STOP=1 -d mydb -f mysql.postgresql.sqlAs chaves estrangeiras são adicionadas como NOT VALID e depois validadas. Se uma linha aponta para uma linha que não existe, o script informa qual restrição, em vez de falhar no meio do caminho.
Para um dump grande, crie o banco de dados antes e execute o script com o psql na mesma máquina do servidor.
Gere o dump do MySQL para o melhor resultado
O conversor lê o que o dump contém. Estas opções mantêm mais dos seus dados nele:
- --default-character-set=utf8mb4 mantém emoji e outros caracteres de 4 bytes, que um dump em utf8 perde antes de o conversor vê-lo.
- --hex-blob escreve as colunas binárias em hexadecimal, o que sobrevive à cópia entre sistemas.
- --routines --triggers --events adicionam esses objetos ao dump, para que o relatório possa listá-los.
O que não é convertido
Visões, gatilhos, procedimentos armazenados, funções e eventos são escritos na linguagem própria de cada banco de dados. Eles aparecem no relatório pelo nome, nunca são descartados em silêncio, para que você possa reescrevê-los para o PostgreSQL.
Comparado com o pgloader
Os mesmos dumps, convertidos e carregados pelo IntoSQL e pelo pgloader 3.6.7 em uma única máquina (um i9-10900K de 10 núcleos com SATA SSD), em outubro de 2026.
- Uma coluna TINYINT(1) com 2, -1 e -5: o pgloader carrega true para cada um, sem aviso. O IntoSQL mantém os números e o relatório explica o motivo.
- Um dump com um valor padrão 'it's', uma tabela com crase no nome, um ENUM com o valor vazio e um TIME acima de 24 horas: o pgloader para. O IntoSQL o converte e o carrega.
- O que o pgloader carrega perdeu as chaves primárias, únicas e estrangeiras e as colunas identity. O IntoSQL mantém todas.
- Um dump de 5.3 GB com 30 milhões de linhas: o pgloader exige restaurá-lo antes em um MySQL em execução, o que leva 18 minutos, e depois fica sem memória com as configurações padrão. O IntoSQL converte o dump em 10 segundos, e o psql carrega o script em cerca de 3 minutos.
- Em 476 dumps do MySQL 5.5 ao 9 e do MariaDB 10.3 ao 12, carregados no PostgreSQL 13 e 18: nenhum erro de carga e nenhum valor alterado sem uma linha no relatório.
Perguntas sobre MySQL para PostgreSQL
Não encontrou a resposta? Escreva para nós pela página de contato e uma pessoa responde. Página de contato
Meu dump é enviado?
Não. O conversor roda no seu navegador e lê o dump do seu disco em partes, então ele nunca sai do seu computador. O app para desktop e a linha de comando também convertem no seu computador.
Qual o tamanho máximo do dump?
O conversor nunca guarda o dump inteiro na memória: ele lê o arquivo duas vezes, por intervalos, e escreve o script à medida que avança. Dumps de vários gigabytes são convertidos no navegador, no app para desktop e com a linha de comando.
Ele lê dumps do MariaDB?
Sim. Dumps do mariadb-dump e do MariaDB 10.3 ao 12 são convertidos da mesma forma, incluindo as colunas INET6, INET4 e UUID do MariaDB.
O que acontece com datas 0000-00-00?
O PostgreSQL não tem data zero, então elas viram NULL e a coluna passa a aceitar NULL. O relatório diz quantas havia em cada coluna.
Os valores de AUTO_INCREMENT são mantidos?
Sim. Cada coluna AUTO_INCREMENT vira uma coluna identity, cada linha mantém seu id, e o próximo id continua depois do maior, ou depois do AUTO_INCREMENT da tabela, se ele for maior.
TINYINT(1) vira boolean?
Só quando todo valor da coluna é 0 ou 1. Se ela contém outros números, como 2 ou -1, continua sendo número e o relatório explica o motivo, para que nenhum valor vire true por acidente.
E os procedimentos armazenados e gatilhos?
Eles aparecem no relatório pelo nome e não são convertidos, porque cada banco de dados os escreve na sua própria linguagem. ON UPDATE CURRENT_TIMESTAMP é a exceção: vira um gatilho do PostgreSQL.
Em quais versões do PostgreSQL o script carrega?
PostgreSQL 13 a 18. O mesmo script carrega em cada uma delas e para no primeiro erro se algo der errado.