Перейти к содержанию
Innovialabs

Как перенести базу данных с MySQL на PostgreSQL

Практика4 мин

Коротко

Миграция с MySQL на PostgreSQL — это не только перенос данных: сначала проверяются типы и SQL-запросы приложения, затем данные переносятся через pgloader на тестовый стенд, сверяются, и только после репетиции приложение переключается в короткое окно с планом отката.

Короткий ответ

Надёжная миграция идёт в пять этапов:

  1. Аудит: схема, типы, запросы приложения, хранимые процедуры.
  2. Репетиция на копии: перенос через pgloader, исправление ошибок.
  3. Адаптация приложения: драйвер, ORM, «сырые» SQL-запросы.
  4. Сверка данных: количество строк, суммы, выборочные записи.
  5. Переключение в окно обслуживания с готовым планом отката.

Сам перенос данных обычно самая простая часть. Больше всего времени уходит на запросы, которые MySQL прощал, а PostgreSQL — нет.

Различия типов

MySQLPostgreSQLКомментарий
INT AUTO_INCREMENTinteger GENERATED BY DEFAULT AS IDENTITY или serialПосле загрузки сдвиньте счётчики
TINYINT(1)booleanЕсли там только 0 и 1
INT UNSIGNEDbigint или integer + CHECKБеззнаковых типов в PostgreSQL нет
DATETIMEtimestampБез часового пояса
TIMESTAMPtimestamptzС учётом часового пояса
DOUBLEdouble precisionДля денег — numeric
ENUM(...)text + CHECK или CREATE TYPE ... AS ENUMНовые значения проще добавлять в CHECK
JSONjsonbИндексируется через GIN
BLOB, LONGBLOBbytea
'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, чтобы планировщик получил статистику.

Переключение с минимальным простоем

Самый практичный вариант для большинства проектов:

  1. Заранее отрепетируйте миграцию и замерьте время переноса.
  2. В окно обслуживания переведите приложение в режим только чтения или остановите запись.
  3. Выполните финальный перенос и сверку.
  4. Переключите строку подключения и перезапустите приложение.
  5. Следите за ошибками и медленными запросами; держите MySQL нетронутым для отката.

Если даже короткая остановка недопустима, используйте репликацию изменений (CDC) — например, Debezium или облачные сервисы миграции. Это сложнее и требует отдельной проверки, поэтому оправдано только для действительно критичных систем.

Частые вопросы

Зависит от объёма данных, числа «сырых» SQL-запросов в коде, хранимых процедур и покрытия тестами. Перенос данных по времени замеряется на репетиции; адаптация приложения обычно занимает больше.

Поможем с этим

Если хотите не разбираться самим, а получить результат — этим мы занимаемся каждый день.

Обсудить проект

Читайте также

Есть идея или задача? Расскажите нам

Ответим в течение рабочего дня и предложим решение. Консультация бесплатная.