Как перенести базу данных с MySQL на PostgreSQL
Практика4 минРедакция Innovialabs
Коротко
Миграция с MySQL на PostgreSQL — это не только перенос данных: сначала проверяются типы и SQL-запросы приложения, затем данные переносятся через pgloader на тестовый стенд, сверяются, и только после репетиции приложение переключается в короткое окно с планом отката.
Короткий ответ
Надёжная миграция идёт в пять этапов:
- Аудит: схема, типы, запросы приложения, хранимые процедуры.
- Репетиция на копии: перенос через pgloader, исправление ошибок.
- Адаптация приложения: драйвер, ORM, «сырые» SQL-запросы.
- Сверка данных: количество строк, суммы, выборочные записи.
- Переключение в окно обслуживания с готовым планом отката.
Сам перенос данных обычно самая простая часть. Больше всего времени уходит на запросы, которые MySQL прощал, а PostgreSQL — нет.
Различия типов
| MySQL | PostgreSQL | Комментарий |
|---|---|---|
INT AUTO_INCREMENT | integer GENERATED BY DEFAULT AS IDENTITY или serial | После загрузки сдвиньте счётчики |
TINYINT(1) | boolean | Если там только 0 и 1 |
INT UNSIGNED | bigint или integer + CHECK | Беззнаковых типов в PostgreSQL нет |
DATETIME | timestamp | Без часового пояса |
TIMESTAMP | timestamptz | С учётом часового пояса |
DOUBLE | double precision | Для денег — numeric |
ENUM(...) | text + CHECK или CREATE TYPE ... AS ENUM | Новые значения проще добавлять в CHECK |
JSON | jsonb | Индексируется через GIN |
BLOB, LONGBLOB | bytea | |
'0000-00-00' | NULL | Таких дат в PostgreSQL не бывает |
Различия синтаксиса и поведения
- Обратные кавычки
`name`→ двойные"name", а лучше имена без кавычек в нижнем регистре. - Регистр: PostgreSQL приводит имена без кавычек к нижнему регистру. Таблица
Users, созданная в кавычках, потребует кавычек везде. LIMIT 10, 20→LIMIT 20 OFFSET 10.IFNULL()→COALESCE(),GROUP_CONCAT()→string_agg().INSERT ... ON DUPLICATE KEY UPDATE→INSERT ... ON CONFLICT (...) DO UPDATE.- Сравнение строк: в MySQL оно часто нечувствительно к регистру из-за collation, в PostgreSQL — чувствительно. Используйте
ILIKE,lower()с индексом по выражению или типcitext. - GROUP BY: PostgreSQL требует, чтобы все неагрегированные столбцы были в
GROUP BYили функционально зависели от ключа. - Строгость типов: MySQL молча приводит типы, а PostgreSQL выдаст ошибку, если сравнить числовой столбец с параметром, переданным как текст.
Прогоните полный набор тестов приложения на PostgreSQL — это самый быстрый способ найти такие запросы.
Перенос через pgloader
pgloader читает схему MySQL, создаёт таблицы, индексы и внешние ключи в PostgreSQL, переносит данные и обновляет последовательности. Быстрый старт одной командой:
pgloader mysql://user:pass@mysql-host/shop postgresql://user:pass@pg-host/shop
Для повторяемых прогонов удобнее файл с настройками:
LOAD DATABASE
FROM mysql://app:secret@mysql-host/shop
INTO postgresql://app:secret@pg-host/shop
WITH include drop, create tables, create indexes,
reset sequences, foreign keys
ALTER SCHEMA 'shop' RENAME TO 'public';
pgloader migrate.load
Что важно знать:
- По умолчанию pgloader кладёт таблицы в схему с именем базы MySQL — отсюда
ALTER SCHEMA. - У pgloader есть правила приведения типов по умолчанию; их можно переопределить в блоке
CAST. - Читайте итоговый отчёт: строки с ошибками он пропускает и показывает отдельно.
- Триггеры, процедуры и представления переносятся не автоматически — их переписывают вручную.
Сверка данных
Не доверяйте фразе «завершено без ошибок». Проверьте:
- Точное количество строк каждой таблицы через
COUNT(*)в обеих базах. Статистикаinformation_schemaв MySQL для InnoDB приблизительна. - Контрольные агрегаты: суммы денежных полей,
MIN/MAXключей и дат. - Выборочные записи: одни и те же 100 случайных id в обеих базах, сравнение построчно.
- Кодировку: кириллица, узбекские апострофы, эмодзи в пользовательских данных.
- Последовательности: следующий id должен быть больше максимального существующего.
После загрузки выполните ANALYZE, чтобы планировщик получил статистику.
Переключение с минимальным простоем
Самый практичный вариант для большинства проектов:
- Заранее отрепетируйте миграцию и замерьте время переноса.
- В окно обслуживания переведите приложение в режим только чтения или остановите запись.
- Выполните финальный перенос и сверку.
- Переключите строку подключения и перезапустите приложение.
- Следите за ошибками и медленными запросами; держите MySQL нетронутым для отката.
Если даже короткая остановка недопустима, используйте репликацию изменений (CDC) — например, Debezium или облачные сервисы миграции. Это сложнее и требует отдельной проверки, поэтому оправдано только для действительно критичных систем.