Konwersja MySQL do PostgreSQL
Upuść zrzut z mysqldump, mariadb-dump, mysqlpump, phpMyAdmin lub Adminer jako .sql lub .sql.gz i pobierz skrypt PostgreSQL z tymi samymi tabelami, kluczami, indeksami i danymi. Każda wartość, której PostgreSQL nie może przechować bez zmian, i każdy obiekt, którego nie przenosi, są wymienione w raporcie.
18 wierszy przekonwertowanych, 8 poprawek, brak odrzuconych
Tabele w pliku shop_2024.sqlZrzut MySQL 8.0.46 · zapisany przez mysqldump · utf8mb4
customers8 wierszy3
7 kolumn 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 wierszy5
9 kolumn 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();
Poprawki
- "id" w tabeli customers jest bez znaku, więc ma typ bigint z ograniczeniem CHECK, że nie jest ujemna.
- "signed_up" w tabeli customers dopuszcza teraz NULL: części jej wartości nie dało się zapisać i są NULL.
- Wartość domyślna "signed_up" w tabeli customers, 0000‑00‑00, nie jest datą akceptowaną przez PostgreSQL, więc kolumna nie ma wartości domyślnej.
- "customer_id" w tabeli orders jest bez znaku, więc ma typ bigint z ograniczeniem CHECK, że nie jest ujemna.
- "id" w tabeli orders jest bez znaku, więc ma typ bigint z ograniczeniem CHECK, że nie jest ujemna.
- "items" w tabeli orders jest bez znaku, więc ma typ integer z ograniczeniem CHECK, że nie jest ujemna.
- W "status" tabeli orders 1 wiersz zawiera pustą wartość, którą MySQL zapisuje dla błędnych danych; ograniczenie CHECK kolumny ją dopuszcza.
- "updated_at" w tabeli orders zmienia się na bieżący czas przy każdej aktualizacji; w PostgreSQL robi to wyzwalacz.
Do sprawdzenia
- W "signed_up" tabeli customers zamieniono na NULL 1 wartość z zerową lub niemożliwą datą, np. 0000‑00‑00, bo PostgreSQL nie zapisuje takich dat. Jeśli te daty coś znaczą, zastąp je w źródle prawdziwymi, zanim zrobisz zrzut.
- W "shipped_at" tabeli orders zamieniono na NULL 1 wartość z zerową lub niemożliwą datą, np. 0000‑00‑00 00:00:00, bo PostgreSQL nie zapisuje takich dat. Jeśli te daty coś znaczą, zastąp je w źródle prawdziwymi, zanim zrobisz zrzut.
Plik jest konwertowany na tym komputerze i nigdy nie jest wysyłany.
Inne przykłady:
Co zmienia się między MySQL a PostgreSQL
Każdy wiersz to reguła stosowana przez konwerter, z wpisem, który dodaje do raportu, gdy reguła coś zmienia w twoich danych.
TINYINT(1), BOOL
boolean, gdy każda wartość to 0 lub 1, w przeciwnym razie smallint
BOOL w MySQL to TINYINT(1), który mieści dowolną liczbę od -128 do 127. Kolumna z samymi 0 i 1 staje się boolean; kolumna z 2 lub -1 pozostaje liczbą, aby żadna wartość nie zmieniła znaczenia.
Wpis w raporcie: bool_kept_numeric
UNSIGNED integers
następny szerszy typ całkowity z CHECK, że wartość nie jest ujemna; BIGINT UNSIGNED to bigint, gdy każda wartość się mieści, w przeciwnym razie numeric(20,0)
PostgreSQL nie ma liczb całkowitych bez znaku. Szerszy typ mieści cały zakres bez znaku, a ograniczenie CHECK blokuje wartości ujemne, tak jak MySQL.
Wpis w raporcie: unsigned_widened
ZEROFILL and display widths
usunięte
Dopełnienie zerami to sposób wyświetlania liczby, a nie jej część. Liczby zostają zachowane; jeśli dopełnienie jest ważne, formatuj je przy odczycie.
Wpis w raporcie: zerofill_dropped
FLOAT(M,D)
real lub double precision, bez skali
Liczby zmiennoprzecinkowe w PostgreSQL nie mają stałej skali. Zapisane wartości są kopiowane bez zmian.
Wpis w raporcie: float_scale_dropped
BIT(1), BIT(n)
boolean, bit(n)
Pojedynczy bit to tak lub nie; szersze pola bitowe zachowują każdy bit.
zero, partial or impossible dates (0000-00-00, 2024-02-30)
NULL
PostgreSQL przechowuje tylko prawdziwe daty kalendarzowe. MySQL zachowuje daty zerowe i niemożliwe, gdy pozwala na to tryb SQL; stają się NULL, a raport je liczy.
Wpis w raporcie: invalid_date_null
NOT NULL columns with values that become NULL
NOT NULL usunięte
Kolumna, której wartości musiały stać się NULL, nie może zachować NOT NULL, bo ładowanie by się zatrzymało.
Wpis w raporcie: not_null_relaxed
DEFAULT '0000-00-00'
brak wartości domyślnej
Wartość domyślna, której PostgreSQL nie może zapisać, jest pomijana, a nie zastępowana zmyśloną datą.
Wpis w raporcie: zero_date_default
TIME
time, gdy każda wartość mieści się w dobie, w przeciwnym razie interval
TIME w MySQL to czas trwania od -838:59:59 do 838:59:59. Kolumna, której wartości mieszczą się w dobie, pozostaje godziną dnia; kolumna z dłuższymi lub ujemnymi wartościami staje się interval.
Wpis w raporcie: time_as_interval
YEAR
smallint z CHECK zakresu
PostgreSQL nie ma typu roku; ograniczenie CHECK zachowuje te same wartości, na które pozwala MySQL.
CHAR(n)
varchar(n)
MySQL usuwa końcowe spacje z wartości CHAR przy odczycie, a char(n) w PostgreSQL dodałby je z powrotem, więc varchar(n) zachowuje wartości tak, jak zwraca je MySQL.
DATETIME
timestamp
DATETIME to wskazanie zegara bez strefy czasowej, jak timestamp w PostgreSQL.
TIMESTAMP
timestamptz w strefie czasowej podanej w zrzucie, UTC, gdy jej nie podaje
TIMESTAMP to moment w czasie. Zrzuty podają strefę, w której go zapisały (mysqldump zapisuje UTC); bez niej wartości są odczytywane jako UTC, a raport o tym mówi.
Wpis w raporcie: timestamp_zone_assumed
ENUM
text z CHECK dozwolonych wartości
Tekst z ograniczeniem CHECK zachowuje te same wartości i łatwiej go zmienić niż typ enum w PostgreSQL.
ENUM holding '' (MySQL's error value)
pusta wartość dopuszczona przez CHECK
MySQL zapisuje pusty ciąg, gdy w łagodnym trybie SQL do ENUM trafia błędna wartość. Te wiersze są zachowywane, a ograniczenie CHECK je dopuszcza.
Wpis w raporcie: enum_invalid_value
ENUM or SET holding values outside its list
bez CHECK
Wartości spoza listy nie przeszłyby ograniczenia CHECK, więc nie jest ono tworzone.
Wpis w raporcie: enum_check_skipped
SET
text[] z CHECK, że każdy element jest na liście
Tablica przechowuje każdy element osobno, więc zapytania mogą sprawdzać przynależność.
JSON
jsonb; json, gdy wartość zawiera \u0000
jsonb to typ, który PostgreSQL dobrze indeksuje i przeszukuje, ale odrzuca sekwencję \u0000, więc kolumna z nią pozostaje json.
Wpis w raporcie: json_not_jsonb
NUL bytes in text
usunięte
Tekst w PostgreSQL nie może zawierać bajtu NUL. Bajt jest usuwany, a raport liczy takie wartości.
Wpis w raporcie: nul_removed
text that isn't valid in its character set
błędne bajty jako znak zastępczy
Tekst w PostgreSQL musi być poprawnym UTF-8.
Wpis w raporcie: invalid_utf8_replaced
text decoded twice (ñ)
skopiowane bez zmian
Zrzut zawiera dokładnie te znaki, więc są kopiowane; raport je wskazuje.
Wpis w raporcie: mojibake_suspected
AUTO_INCREMENT
GENERATED BY DEFAULT AS IDENTITY, z licznikiem ustawionym za skopiowanymi id
Kolumny identity przyjmują skopiowane id, a licznik idzie dalej od najwyższego z nich lub od AUTO_INCREMENT ze zrzutu, zależnie od tego, która wartość jest wyższa.
MyISAM AUTO_INCREMENT on the second column of a key
jeden licznik dla tabeli
MyISAM liczy osobno w każdej grupie pierwszej kolumny klucza; PostgreSQL ma jeden licznik na kolumnę.
Wpis w raporcie: myisam_group_autoincrement
ON UPDATE CURRENT_TIMESTAMP
wyzwalacz BEFORE UPDATE
Wyzwalacz ustawia kolumnę na bieżący czas, gdy wiersz się zmienia, a aktualizacja jej nie ustawiła, tak jak MySQL.
Wpis w raporcie: on_update_trigger
generated columns
STORED
PostgreSQL od 13 do 17 tylko przechowuje kolumny generowane; formuła jest ta sama.
Wpis w raporcie: virtual_as_stored
generated columns with functions PostgreSQL lacks
zwykła kolumna
Formuła, której nie da się zapisać dla PostgreSQL, zostawia zwykłą kolumnę i wpis w raporcie.
Wpis w raporcie: generated_not_translated
INVISIBLE columns
zwykłe kolumny
PostgreSQL nie ma niewidocznych kolumn.
Wpis w raporcie: invisible_column
CHECK constraints
przetłumaczone, wewnątrz CREATE TABLE
Ograniczenia CHECK wewnątrz definicji są sprawdzane przy ładowaniu każdego wiersza, a nie osobnym skanem dla każdego z nich na końcu.
CHECK constraints with functions PostgreSQL lacks
nie jest tworzone
Ograniczenie CHECK, którego nie da się zapisać dla PostgreSQL, jest pomijane i zgłaszane w raporcie.
Wpis w raporcie: check_skipped
CHECK ... NOT ENFORCED
nie jest tworzone
MySQL przechowuje te ograniczenia bez ich stosowania, więc wiersze mogą je łamać.
Wpis w raporcie: check_not_enforced
defaults with functions PostgreSQL lacks
brak wartości domyślnej
Wartość domyślna, której nie da się zapisać dla PostgreSQL, jest pomijana i zgłaszana w raporcie.
Wpis w raporcie: default_not_translated
keys and indexes on a prefix, c(20)
indeks na left(c, 20)
PostgreSQL indeksuje wyrażenia, więc indeksowany jest ten sam prefiks.
Wpis w raporcie: prefix_unique
FULLTEXT indexes
GIN na to_tsvector('simple', ...)
Wyszukiwanie pełnotekstowe działa z @@ zamiast MATCH ... AGAINST.
Wpis w raporcie: fulltext_as_gin
indexes on values over 2,000 bytes
hash
Wpis btree nie pomieści tak długich wartości; indeks hash nadal znajduje równe wartości.
Wpis w raporcie: index_on_wide_values
SPATIAL indexes
nie jest tworzony bez PostGIS
Indeksy przestrzenne wymagają PostGIS.
Wpis w raporcie: spatial_index_skipped
functional indexes PostgreSQL can't write
nie jest tworzony
Indeks na wyrażeniu, którego nie da się zapisać dla PostgreSQL, jest pomijany i zgłaszany w raporcie.
Wpis w raporcie: index_skipped
foreign keys
po danych, jako NOT VALID, potem sprawdzany z ostrzeżeniem o osieroconych wierszach
Zrzuty ładują się z FOREIGN_KEY_CHECKS=0, a MyISAM nigdy nie sprawdza kluczy, więc wiersze bez rodzica są częste. Klucz jest tworzony najpierw i sprawdzany na końcu; osierocone wiersze zostawiają go jako NOT VALID z ostrzeżeniem, zamiast zatrzymać ładowanie.
foreign keys to columns without a unique key
nie jest tworzony
PostgreSQL wymaga klucza unikalnego na kolumnach, do których się odwołuje; InnoDB nie.
Wpis w raporcie: foreign_key_skipped
table and column names
małe litery, gdy nazwy pozostają unikalne
Nazwy z małych liter można w PostgreSQL pisać bez cudzysłowów.
Wpis w raporcie: identifier_renamed
names over 63 bytes
skrócone z hashem
Nazwy w PostgreSQL mają do 63 bajtów; hash odróżnia skrócone nazwy od siebie.
Wpis w raporcie: name_shortened
GEOMETRY, POINT, POLYGON...
WKB w bytea
Bez PostGIS standardowy format well-known binary zachowuje dokładnie każdą geometrię.
Wpis w raporcie: geometry_as_wkb
INET4, INET6, UUID (MariaDB)
inet, uuid
PostgreSQL ma te same typy.
BINARY, VARBINARY, BLOB
bytea
Bajty pozostają bajtami.
PARTITION BY
jedna tabela
Wiersze są te same; jeśli trzeba, podziel tabelę na partycje ponownie w PostgreSQL.
Wpis w raporcie: partitions_dropped
WITH SYSTEM VERSIONING (MariaDB)
tabela z bieżącymi wierszami
Zrzut zawiera tylko bieżące wiersze.
Wpis w raporcie: system_versioning_dropped
MyISAM, MEMORY and other engines
zwykła tabela
PostgreSQL ma jeden silnik tabel.
Wpis w raporcie: engine_dropped
views, triggers, procedures, functions and events
wymienione w raporcie
Ich kod jest napisany w języku MySQL, więc trafiają na listę do ręcznego odtworzenia.
Wpis w raporcie: not_converted_objects
dumps without CREATE TABLE
typy odczytane z wartości
Zrzut zrobiony z --no-create-info zawiera tylko wiersze, więc typy pochodzą z wartości.
Wpis w raporcie: no_schema
geometries in a dump without CREATE TABLE
WKB w bytea
Format geometrii MySQL jest rozpoznawany po wartościach i zapisywany jako standardowe WKB.
Wpis w raporcie: geometry_inferred
_ci collations
dokładne porównanie
PostgreSQL porównuje tekst dokładnie, chyba że ustawisz inaczej.
Wpis w raporcie: case_insensitive_collation
SET NAMES utf8 from utf8mb4 tables
przekonwertowane, z ostrzeżeniem
mysqldump z MySQL 5.x domyślnie zapisuje 3-bajtowe utf8 i zastępuje każde emoji znakiem zapytania, zanim jakikolwiek konwerter zobaczy plik.
Wpis w raporcie: dump_lost_four_byte_chars
dumps without SET NAMES whose text isn't UTF-8
odczytane jako Latin-1 (Windows-1252)
latin1 w MySQL to Windows-1252, zwykłe kodowanie takich zrzutów.
Wpis w raporcie: charset_guessed
sjis, cp932, gbk, big5 dumps
odczytane jako UTF-8, z ostrzeżeniem
Te kodowania nie są jeszcze dekodowane.
Wpis w raporcie: unsupported_charset
UNHEX('..'), CONVERT(b'..', UNSIGNED), CONVERT(".." USING x), ST_GeomFromText('..') in rows
obliczone
Niektóre narzędzia do zrzutów zapisują wartości jako takie wywołania; są odczytywane jako wartości, które zwracają, przy czym w geograficznym WKT najpierw jest długość geograficzna.
other expressions in rows
NULL
Konwerter, który nie uruchamia bazy danych, nie może ich obliczyć.
Wpis w raporcie: expression_value
DEFAULT in VALUES
wartość domyślna kolumny
Dosłowna wartość domyślna jest kopiowana do wiersza.
Wpis w raporcie: default_keyword_filled
DEFAULT in VALUES without a literal default
NULL
Nie ma wartości do skopiowania.
Wpis w raporcie: default_keyword_null
REPLACE and INSERT IGNORE
zwykłe wiersze
Zrzut ma jeden wiersz na klucz, więc wiersze są te same.
Wpis w raporcie: replace_as_insert
types IntoSQL doesn't know
text
Tekst mieści każdą wartość; zmień typ po załadowaniu.
Wpis w raporcie: unknown_type
values that don't fit their column
NULL
Uszkodzony lub ręcznie pisany zrzut może zawierać wartości, których kolumna nie przyjmie; stają się NULL, aby reszta się załadowała.
Wpis w raporcie: invalid_value_null
unreadable geometries
NULL
Bajtów, które nie są geometrią, nie da się zapisać jako geometrii.
Wpis w raporcie: bad_geometry
CREATE SEQUENCE (MariaDB)
CREATE SEQUENCE z bieżącą wartością
Sekwencje w PostgreSQL przyjmują te same opcje.
| W MySQL | W PostgreSQL | Wpis w raporcie |
|---|---|---|
| TINYINT(1), BOOL | boolean, gdy każda wartość to 0 lub 1, w przeciwnym razie smallintBOOL w MySQL to TINYINT(1), który mieści dowolną liczbę od -128 do 127. Kolumna z samymi 0 i 1 staje się boolean; kolumna z 2 lub -1 pozostaje liczbą, aby żadna wartość nie zmieniła znaczenia. | bool_kept_numeric |
| UNSIGNED integers | następny szerszy typ całkowity z CHECK, że wartość nie jest ujemna; BIGINT UNSIGNED to bigint, gdy każda wartość się mieści, w przeciwnym razie numeric(20,0)PostgreSQL nie ma liczb całkowitych bez znaku. Szerszy typ mieści cały zakres bez znaku, a ograniczenie CHECK blokuje wartości ujemne, tak jak MySQL. | unsigned_widened |
| ZEROFILL and display widths | usunięteDopełnienie zerami to sposób wyświetlania liczby, a nie jej część. Liczby zostają zachowane; jeśli dopełnienie jest ważne, formatuj je przy odczycie. | zerofill_dropped |
| FLOAT(M,D) | real lub double precision, bez skaliLiczby zmiennoprzecinkowe w PostgreSQL nie mają stałej skali. Zapisane wartości są kopiowane bez zmian. | float_scale_dropped |
| BIT(1), BIT(n) | boolean, bit(n)Pojedynczy bit to tak lub nie; szersze pola bitowe zachowują każdy bit. | |
| zero, partial or impossible dates (0000-00-00, 2024-02-30) | NULLPostgreSQL przechowuje tylko prawdziwe daty kalendarzowe. MySQL zachowuje daty zerowe i niemożliwe, gdy pozwala na to tryb SQL; stają się NULL, a raport je liczy. | invalid_date_null |
| NOT NULL columns with values that become NULL | NOT NULL usunięteKolumna, której wartości musiały stać się NULL, nie może zachować NOT NULL, bo ładowanie by się zatrzymało. | not_null_relaxed |
| DEFAULT '0000-00-00' | brak wartości domyślnejWartość domyślna, której PostgreSQL nie może zapisać, jest pomijana, a nie zastępowana zmyśloną datą. | zero_date_default |
| TIME | time, gdy każda wartość mieści się w dobie, w przeciwnym razie intervalTIME w MySQL to czas trwania od -838:59:59 do 838:59:59. Kolumna, której wartości mieszczą się w dobie, pozostaje godziną dnia; kolumna z dłuższymi lub ujemnymi wartościami staje się interval. | time_as_interval |
| YEAR | smallint z CHECK zakresuPostgreSQL nie ma typu roku; ograniczenie CHECK zachowuje te same wartości, na które pozwala MySQL. | |
| CHAR(n) | varchar(n)MySQL usuwa końcowe spacje z wartości CHAR przy odczycie, a char(n) w PostgreSQL dodałby je z powrotem, więc varchar(n) zachowuje wartości tak, jak zwraca je MySQL. | |
| DATETIME | timestampDATETIME to wskazanie zegara bez strefy czasowej, jak timestamp w PostgreSQL. | |
| TIMESTAMP | timestamptz w strefie czasowej podanej w zrzucie, UTC, gdy jej nie podajeTIMESTAMP to moment w czasie. Zrzuty podają strefę, w której go zapisały (mysqldump zapisuje UTC); bez niej wartości są odczytywane jako UTC, a raport o tym mówi. | timestamp_zone_assumed |
| ENUM | text z CHECK dozwolonych wartościTekst z ograniczeniem CHECK zachowuje te same wartości i łatwiej go zmienić niż typ enum w PostgreSQL. | |
| ENUM holding '' (MySQL's error value) | pusta wartość dopuszczona przez CHECKMySQL zapisuje pusty ciąg, gdy w łagodnym trybie SQL do ENUM trafia błędna wartość. Te wiersze są zachowywane, a ograniczenie CHECK je dopuszcza. | enum_invalid_value |
| ENUM or SET holding values outside its list | bez CHECKWartości spoza listy nie przeszłyby ograniczenia CHECK, więc nie jest ono tworzone. | enum_check_skipped |
| SET | text[] z CHECK, że każdy element jest na liścieTablica przechowuje każdy element osobno, więc zapytania mogą sprawdzać przynależność. | |
| JSON | jsonb; json, gdy wartość zawiera \u0000jsonb to typ, który PostgreSQL dobrze indeksuje i przeszukuje, ale odrzuca sekwencję \u0000, więc kolumna z nią pozostaje json. | json_not_jsonb |
| NUL bytes in text | usunięteTekst w PostgreSQL nie może zawierać bajtu NUL. Bajt jest usuwany, a raport liczy takie wartości. | nul_removed |
| text that isn't valid in its character set | błędne bajty jako znak zastępczyTekst w PostgreSQL musi być poprawnym UTF-8. | invalid_utf8_replaced |
| text decoded twice (ñ) | skopiowane bez zmianZrzut zawiera dokładnie te znaki, więc są kopiowane; raport je wskazuje. | mojibake_suspected |
| AUTO_INCREMENT | GENERATED BY DEFAULT AS IDENTITY, z licznikiem ustawionym za skopiowanymi idKolumny identity przyjmują skopiowane id, a licznik idzie dalej od najwyższego z nich lub od AUTO_INCREMENT ze zrzutu, zależnie od tego, która wartość jest wyższa. | |
| MyISAM AUTO_INCREMENT on the second column of a key | jeden licznik dla tabeliMyISAM liczy osobno w każdej grupie pierwszej kolumny klucza; PostgreSQL ma jeden licznik na kolumnę. | myisam_group_autoincrement |
| ON UPDATE CURRENT_TIMESTAMP | wyzwalacz BEFORE UPDATEWyzwalacz ustawia kolumnę na bieżący czas, gdy wiersz się zmienia, a aktualizacja jej nie ustawiła, tak jak MySQL. | on_update_trigger |
| generated columns | STOREDPostgreSQL od 13 do 17 tylko przechowuje kolumny generowane; formuła jest ta sama. | virtual_as_stored |
| generated columns with functions PostgreSQL lacks | zwykła kolumnaFormuła, której nie da się zapisać dla PostgreSQL, zostawia zwykłą kolumnę i wpis w raporcie. | generated_not_translated |
| INVISIBLE columns | zwykłe kolumnyPostgreSQL nie ma niewidocznych kolumn. | invisible_column |
| CHECK constraints | przetłumaczone, wewnątrz CREATE TABLEOgraniczenia CHECK wewnątrz definicji są sprawdzane przy ładowaniu każdego wiersza, a nie osobnym skanem dla każdego z nich na końcu. | |
| CHECK constraints with functions PostgreSQL lacks | nie jest tworzoneOgraniczenie CHECK, którego nie da się zapisać dla PostgreSQL, jest pomijane i zgłaszane w raporcie. | check_skipped |
| CHECK ... NOT ENFORCED | nie jest tworzoneMySQL przechowuje te ograniczenia bez ich stosowania, więc wiersze mogą je łamać. | check_not_enforced |
| defaults with functions PostgreSQL lacks | brak wartości domyślnejWartość domyślna, której nie da się zapisać dla PostgreSQL, jest pomijana i zgłaszana w raporcie. | default_not_translated |
| keys and indexes on a prefix, c(20) | indeks na left(c, 20)PostgreSQL indeksuje wyrażenia, więc indeksowany jest ten sam prefiks. | prefix_unique |
| FULLTEXT indexes | GIN na to_tsvector('simple', ...)Wyszukiwanie pełnotekstowe działa z @@ zamiast MATCH ... AGAINST. | fulltext_as_gin |
| indexes on values over 2,000 bytes | hashWpis btree nie pomieści tak długich wartości; indeks hash nadal znajduje równe wartości. | index_on_wide_values |
| SPATIAL indexes | nie jest tworzony bez PostGISIndeksy przestrzenne wymagają PostGIS. | spatial_index_skipped |
| functional indexes PostgreSQL can't write | nie jest tworzonyIndeks na wyrażeniu, którego nie da się zapisać dla PostgreSQL, jest pomijany i zgłaszany w raporcie. | index_skipped |
| foreign keys | po danych, jako NOT VALID, potem sprawdzany z ostrzeżeniem o osieroconych wierszachZrzuty ładują się z FOREIGN_KEY_CHECKS=0, a MyISAM nigdy nie sprawdza kluczy, więc wiersze bez rodzica są częste. Klucz jest tworzony najpierw i sprawdzany na końcu; osierocone wiersze zostawiają go jako NOT VALID z ostrzeżeniem, zamiast zatrzymać ładowanie. | |
| foreign keys to columns without a unique key | nie jest tworzonyPostgreSQL wymaga klucza unikalnego na kolumnach, do których się odwołuje; InnoDB nie. | foreign_key_skipped |
| table and column names | małe litery, gdy nazwy pozostają unikalneNazwy z małych liter można w PostgreSQL pisać bez cudzysłowów. | identifier_renamed |
| names over 63 bytes | skrócone z hashemNazwy w PostgreSQL mają do 63 bajtów; hash odróżnia skrócone nazwy od siebie. | name_shortened |
| GEOMETRY, POINT, POLYGON... | WKB w byteaBez PostGIS standardowy format well-known binary zachowuje dokładnie każdą geometrię. | geometry_as_wkb |
| INET4, INET6, UUID (MariaDB) | inet, uuidPostgreSQL ma te same typy. | |
| BINARY, VARBINARY, BLOB | byteaBajty pozostają bajtami. | |
| PARTITION BY | jedna tabelaWiersze są te same; jeśli trzeba, podziel tabelę na partycje ponownie w PostgreSQL. | partitions_dropped |
| WITH SYSTEM VERSIONING (MariaDB) | tabela z bieżącymi wierszamiZrzut zawiera tylko bieżące wiersze. | system_versioning_dropped |
| MyISAM, MEMORY and other engines | zwykła tabelaPostgreSQL ma jeden silnik tabel. | engine_dropped |
| views, triggers, procedures, functions and events | wymienione w raporcieIch kod jest napisany w języku MySQL, więc trafiają na listę do ręcznego odtworzenia. | not_converted_objects |
| dumps without CREATE TABLE | typy odczytane z wartościZrzut zrobiony z --no-create-info zawiera tylko wiersze, więc typy pochodzą z wartości. | no_schema |
| geometries in a dump without CREATE TABLE | WKB w byteaFormat geometrii MySQL jest rozpoznawany po wartościach i zapisywany jako standardowe WKB. | geometry_inferred |
| _ci collations | dokładne porównaniePostgreSQL porównuje tekst dokładnie, chyba że ustawisz inaczej. | case_insensitive_collation |
| SET NAMES utf8 from utf8mb4 tables | przekonwertowane, z ostrzeżeniemmysqldump z MySQL 5.x domyślnie zapisuje 3-bajtowe utf8 i zastępuje każde emoji znakiem zapytania, zanim jakikolwiek konwerter zobaczy plik. | dump_lost_four_byte_chars |
| dumps without SET NAMES whose text isn't UTF-8 | odczytane jako Latin-1 (Windows-1252)latin1 w MySQL to Windows-1252, zwykłe kodowanie takich zrzutów. | charset_guessed |
| sjis, cp932, gbk, big5 dumps | odczytane jako UTF-8, z ostrzeżeniemTe kodowania nie są jeszcze dekodowane. | unsupported_charset |
| UNHEX('..'), CONVERT(b'..', UNSIGNED), CONVERT(".." USING x), ST_GeomFromText('..') in rows | obliczoneNiektóre narzędzia do zrzutów zapisują wartości jako takie wywołania; są odczytywane jako wartości, które zwracają, przy czym w geograficznym WKT najpierw jest długość geograficzna. | |
| other expressions in rows | NULLKonwerter, który nie uruchamia bazy danych, nie może ich obliczyć. | expression_value |
| DEFAULT in VALUES | wartość domyślna kolumnyDosłowna wartość domyślna jest kopiowana do wiersza. | default_keyword_filled |
| DEFAULT in VALUES without a literal default | NULLNie ma wartości do skopiowania. | default_keyword_null |
| REPLACE and INSERT IGNORE | zwykłe wierszeZrzut ma jeden wiersz na klucz, więc wiersze są te same. | replace_as_insert |
| types IntoSQL doesn't know | textTekst mieści każdą wartość; zmień typ po załadowaniu. | unknown_type |
| values that don't fit their column | NULLUszkodzony lub ręcznie pisany zrzut może zawierać wartości, których kolumna nie przyjmie; stają się NULL, aby reszta się załadowała. | invalid_value_null |
| unreadable geometries | NULLBajtów, które nie są geometrią, nie da się zapisać jako geometrii. | bad_geometry |
| CREATE SEQUENCE (MariaDB) | CREATE SEQUENCE z bieżącą wartościąSekwencje w PostgreSQL przyjmują te same opcje. |
Załaduj skrypt do PostgreSQL
Skrypt tworzy tabele, kopiuje wiersze, a potem dodaje klucze, indeksy i klucze obce, więc ładuje się szybko i zatrzymuje przy pierwszym błędzie.
psql -v ON_ERROR_STOP=1 -d mydb -f mysql.postgresql.sqlKlucze obce są dodawane jako NOT VALID, a potem sprawdzane. Jeśli wiersz wskazuje na nieistniejący wiersz, skrypt podaje, którego ograniczenia to dotyczy, zamiast przerwać w połowie.
Przy dużym zrzucie najpierw utwórz bazę danych i uruchom skrypt przez psql na tej samej maszynie co serwer.
Jak zrobić zrzut MySQL dla najlepszego wyniku
Konwerter odczytuje to, co zawiera zrzut. Te opcje zachowują w nim więcej twoich danych:
- --default-character-set=utf8mb4 zachowuje emoji i inne 4-bajtowe znaki, które zrzut utf8 traci, zanim zobaczy go konwerter.
- --hex-blob zapisuje kolumny binarne szesnastkowo, co przetrwa kopiowanie między systemami.
- --routines --triggers --events dodają te obiekty do zrzutu, aby raport mógł je wymienić.
Co nie jest konwertowane
Widoki, wyzwalacze, procedury składowane, funkcje i zdarzenia są pisane we własnym języku każdej bazy danych. Raport wymienia je z nazwy i nigdy nie pomija ich po cichu, więc możesz je przepisać dla PostgreSQL.
Porównanie z pgloader
Te same zrzuty, przekonwertowane i załadowane przez IntoSQL i przez pgloader 3.6.7 na jednej maszynie (10-rdzeniowy i9-10900K z SATA SSD), w październiku 2026.
- Kolumna TINYINT(1) z wartościami 2, -1 i -5: pgloader ładuje dla każdej true, bez ostrzeżenia. IntoSQL zachowuje liczby, a raport wyjaśnia dlaczego.
- Zrzut z wartością domyślną 'it's', tabelą z grawisem w nazwie, ENUM z pustą wartością i TIME powyżej 24 godzin: pgloader się zatrzymuje. IntoSQL go konwertuje i ładuje.
- To, co ładuje pgloader, traci klucze główne, unikalne i obce oraz kolumny identity. IntoSQL zachowuje każdy z nich.
- Zrzut 5.3 GB z 30 milionami wierszy: pgloader wymaga najpierw odtworzenia go w działającym MySQL, co zajmuje 18 minut, a potem przy ustawieniach domyślnych brakuje mu pamięci. IntoSQL konwertuje zrzut w 10 sekund, a psql ładuje skrypt w około 3 minuty.
- W 476 zrzutach z MySQL od 5.5 do 9 i MariaDB od 10.3 do 12, załadowanych do PostgreSQL 13 i 18: bez błędów ładowania, a żadna wartość nie zmieniła się bez wpisu w raporcie.
Pytania o konwersję MySQL do PostgreSQL
Nie ma tu odpowiedzi? Napisz do nas przez stronę kontaktu, a odpowie człowiek. Strona kontaktu
Czy mój zrzut jest wysyłany?
Nie. Konwerter działa w przeglądarce i odczytuje zrzut z dysku po kawałku, więc nigdy nie opuszcza on twojego komputera. Aplikacja na komputer i wiersz poleceń też konwertują na twoim komputerze.
Jak duży może być zrzut?
Konwerter nigdy nie trzyma całego zrzutu w pamięci: czyta plik dwa razy, fragmentami, i zapisuje skrypt na bieżąco. Zrzuty wielogigabajtowe konwertują się w przeglądarce, w aplikacji na komputer i w wierszu poleceń.
Czy odczytuje zrzuty MariaDB?
Tak. Zrzuty z mariadb-dump i z MariaDB od 10.3 do 12 są konwertowane tak samo, łącznie z kolumnami INET6, INET4 i UUID z MariaDB.
Co dzieje się z datami 0000-00-00?
PostgreSQL nie ma daty zerowej, więc stają się NULL, a kolumna przyjmuje NULL. Raport podaje, ile ich było w każdej kolumnie.
Czy wartości AUTO_INCREMENT są zachowywane?
Tak. Każda kolumna AUTO_INCREMENT staje się kolumną identity, każdy wiersz zachowuje swoje id, a kolejne id idzie po najwyższym albo po AUTO_INCREMENT tabeli, jeśli ten jest wyższy.
Czy TINYINT(1) staje się boolean?
Tylko gdy każda wartość w kolumnie to 0 lub 1. Jeśli zawiera inne liczby, np. 2 lub -1, pozostaje liczbą, a raport wyjaśnia dlaczego, więc żadna wartość nie zmieni się przypadkiem w true.
A procedury składowane i wyzwalacze?
Raport wymienia je z nazwy, ale nie są konwertowane, bo każda baza danych zapisuje je we własnym języku. Wyjątkiem jest ON UPDATE CURRENT_TIMESTAMP: staje się wyzwalaczem PostgreSQL.
Na jakich wersjach PostgreSQL ładuje się skrypt?
PostgreSQL od 13 do 18. Ten sam skrypt ładuje się na każdej z nich i zatrzymuje przy pierwszym błędzie, jeśli coś pójdzie nie tak.