PostgreSQL или MySQL: подробное сравнение и что выбрать

Чем PostgreSQL и MySQL отличаются на практике: четырнадцать критериев, какую базу брать под задачу, одни и те же задачи на обоих диалектах, переезд с MySQL на PostgreSQL и частые ошибки.

Стек и технологии Обновлено

Коротко

PostgreSQL и MySQL — две самые популярные бесплатные реляционные базы, и обе надёжны для типичного сайта. PostgreSQL строже и богаче: изменения схемы в транзакции, RETURNING, jsonb с индексами, массивы, диапазоны и расширения вроде PostGIS и pgvector — это выбор по умолчанию для сервисов, SaaS, сложных данных и ИИ-поиска. MySQL проще в эксплуатации, дешевле на одно подключение, есть на любом виртуальном хостинге и родной для WordPress, 1С-Битрикс и большинства CMS. Берите PostgreSQL, когда данные сложные и будут расти; MySQL — когда проект живёт на CMS или так диктует хостинг.

Коротко: что выбрать

Для нового сервиса, SaaS, магазина на своём коде и всего, где данные сложные, берите PostgreSQL. Она строже охраняет данные, даёт больше SQL и типов и дорастает до геоданных, векторного поиска и временных рядов через расширения — без второй базы.

Для WordPress и большинства готовых CMS, для недорогого виртуального хостинга и для команды, которая уже хорошо держит MySQL, разумный выбор — MySQL. Она проще в эксплуатации, дёшево держит много подключений и у неё очень зрелая репликация. Для типичного сайта ни один выбор не ошибка — ошибки начинаются, когда база спорит с задачей.

  • Сложные данные и рост — PostgreSQL
  • CMS и виртуальный хостинг — MySQL
  • Обе бесплатные и надёжные

PostgreSQL и MySQL: подробное сравнение

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

КритерийPostgreSQLMySQL
Владелец и лицензия сообщество, PostgreSQL License Oracle, GPL v2 или коммерческая лицензия
Строгость данных строгая по устройству строгая в режиме по умолчанию с 5.7; старые установки бывают мягкими
Изменения схемы внутри транзакции, можно откатить каждая команда атомарна, но сама фиксирует транзакцию
Вернуть вставленные строки RETURNING при вставке, изменении и удалении нет; LAST_INSERT_ID() и второй запрос
JSON jsonb с индексом GIN по любому ключу JSON, индексы через вычисляемые колонки
Типы данных массивы, диапазоны, uuid, inet, свои типы классический набор, плюс JSON и пространственные типы
Индексы B-tree, GIN, GiST, BRIN; частичные и по выражению B-tree, полнотекстовые, пространственные; по выражению, без частичных
Расширения PostGIS, pgvector, TimescaleDB и сотни других гораздо меньше; что есть — встроено
Векторный поиск pgvector: индексы и миллионы векторов тип VECTOR в MySQL 9; векторные индексы — в платном облаке
Подключения процесс на подключение, нужен пулер поток на подключение, дешевле
Обновление строк новые версии строк, их убирает VACUUM на месте с журналом отката, без вакуума
Репликация и масштабирование потоковая и логическая; Patroni, Citus очень зрелая; InnoDB Cluster, Vitess
Хостинг любой VPS и облако; реже на дешёвом виртуальном хостинге на любом хостинге, включая самый дешёвый
Типичное окружение Django, Rails, сервисы и SaaS WordPress, Joomla, Magento, 1С-Битрикс

6 различий, которые чувствуются в работе

Таблица — про возможности, а здесь — как они проявляются в настоящем проекте.

  1. Миграции можно откатить

    В PostgreSQL неудачная миграция откатывается целиком. В MySQL наполовину применённую миграцию приходится доделывать или отменять руками.

  2. Один запрос вместо двух

    RETURNING сразу возвращает новую строку с её номером и значениями по умолчанию; MySQL нужен второй запрос.

  3. JSON без подготовки

    Один индекс GIN в PostgreSQL покрывает любой ключ; в MySQL каждому ключу для поиска нужна своя вычисляемая колонка.

  4. Одна база вместо трёх

    Геоданные, векторы и временные ряды приходят в PostgreSQL расширениями; с MySQL это обычно отдельные системы.

  5. Цена подключения

    Сотни прямых подключений для MySQL дёшевы; PostgreSQL с первого дня нужен пулер перед базой.

  6. Обслуживание

    PostgreSQL требует настроить автовакуум для нагруженных таблиц; у MySQL вакуума нет, и её чуть проще содержать.

Какую базу брать под задачу

Двенадцать типичных проектов с рекомендацией и причиной.

ЗадачаБратьПочему
Сайт на WordPress MySQL CMS поддерживает только MySQL и MariaDB
Магазин на готовой платформе MySQL большинство платформ магазинов сделаны под неё
Магазин или каталог на своём коде PostgreSQL jsonb для характеристик, строгие данные, богатый SQL
SaaS или веб-сервис PostgreSQL миграции в транзакции, типы и рост через расширения
Корпоративный сайт любая данных мало; часто хватает и SQLite
Карты, зоны доставки, «рядом» PostgreSQL PostGIS — стандарт для геоданных
ИИ-поиск и RAG PostgreSQL pgvector рядом с данными
Отчёты и аналитика в базе PostgreSQL богаче SQL, оконные функции, частичные индексы
Очень много простых чтений любая обе быстрые; MySQL дешевле держит подключения
Дешёвый виртуальный хостинг MySQL PostgreSQL там есть не всегда
Команда, которая хорошо знает MySQL MySQL опыт важнее небольших различий
Миллиарды событий для аналитики ни та ни другая колоночная база вроде ClickHouse

Одни задачи в PostgreSQL и MySQL: 3 примера

Каждый пример решает одну задачу в обеих базах. Проверены на PostgreSQL 16 и MySQL 8.4 — результаты в комментариях.

Вставить или обновить

Синтаксис разный, смысл один: если товар уже есть, прибавить к остатку.

upsert.sql
-- PostgreSQL: добавить к остатку или завести товар, если он новый
CREATE TABLE stock (
    sku text PRIMARY KEY,
    qty integer NOT NULL CHECK (qty >= 0)
);

INSERT INTO stock (sku, qty) VALUES ('A-100', 5)
ON CONFLICT (sku) DO UPDATE SET qty = stock.qty + EXCLUDED.qty;

INSERT INTO stock (sku, qty) VALUES ('A-100', 3)
ON CONFLICT (sku) DO UPDATE SET qty = stock.qty + EXCLUDED.qty;

SELECT sku, qty FROM stock;   -- A-100 | 8

-- MySQL 8: то же самое через ON DUPLICATE KEY
CREATE TABLE stock (
    sku VARCHAR(32) PRIMARY KEY,
    qty INT NOT NULL CHECK (qty >= 0)
);

INSERT INTO stock (sku, qty) VALUES ('A-100', 5) AS new
ON DUPLICATE KEY UPDATE qty = stock.qty + new.qty;

INSERT INTO stock (sku, qty) VALUES ('A-100', 3) AS new
ON DUPLICATE KEY UPDATE qty = stock.qty + new.qty;

SELECT sku, qty FROM stock;   -- A-100 | 8

Поиск внутри JSON

PostgreSQL индексирует весь документ сразу, MySQL — выбранный ключ через вычисляемую колонку.

json.sql
-- PostgreSQL: один индекс GIN покрывает любой ключ внутри jsonb
CREATE TABLE orders (
    id      bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    details jsonb NOT NULL DEFAULT '{}'
);
CREATE INDEX orders_details_idx ON orders USING gin (details);

INSERT INTO orders (details)
VALUES ('{"delivery": "courier", "floor": 5}'), ('{"delivery": "pickup"}');

SELECT id FROM orders WHERE details @> '{"delivery": "courier"}';   -- 1

-- MySQL 8: поле JSON индексируют через вычисляемую колонку
CREATE TABLE orders (
    id       BIGINT AUTO_INCREMENT PRIMARY KEY,
    details  JSON NOT NULL,
    delivery VARCHAR(20) AS (details->>'$.delivery') STORED,
    INDEX (delivery)
);

INSERT INTO orders (details)
VALUES ('{"delivery": "courier", "floor": 5}'), ('{"delivery": "pickup"}');

SELECT id FROM orders WHERE delivery = 'courier';   -- 1

Откат миграции

Самое заметное на практике различие: PostgreSQL отменяет изменение схемы, а MySQL его уже зафиксировала.

migration.sql
-- PostgreSQL: изменение схемы внутри транзакции можно откатить
BEGIN;
ALTER TABLE stock ADD COLUMN reserved integer NOT NULL DEFAULT 0;
-- миграция пошла не так: откатываем, и колонки как не бывало
ROLLBACK;

SELECT column_name FROM information_schema.columns
WHERE table_name = 'stock'
ORDER BY ordinal_position;   -- sku, qty

-- MySQL 8: ALTER TABLE сам фиксирует транзакцию
START TRANSACTION;
ALTER TABLE stock ADD COLUMN reserved INT NOT NULL DEFAULT 0;
ROLLBACK;   -- поздно: колонка уже добавлена

SELECT column_name FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = 'stock'
ORDER BY ordinal_position;   -- sku, qty, reserved

Переезд с MySQL на PostgreSQL: что учесть

Переезд частый и хорошо изученный, но несколько различий ломают код незаметно.

  1. 01

    pgloader берёт рутину на себя

    Он переносит схему и данные и сам переводит большинство типов.

  2. 02

    Upsert переписывается

    ON DUPLICATE KEY UPDATE превращается в ON CONFLICT … DO UPDATE.

  3. 03

    Кавычки вокруг имён

    Обратные кавычки MySQL становятся двойными, а имена без кавычек PostgreSQL приводит к нижнему регистру.

  4. 04

    Нулевые даты

    0000-00-00 в PostgreSQL не существует — такие значения до переезда превращают в NULL.

  5. 05

    Регистр и сортировка

    MySQL часто сравнивает строки без учёта регистра, PostgreSQL — с учётом: поиску по почте или имени нужен lower() или citext.

  6. 06

    Пулер до запуска

    Коду, который открывал к MySQL сотни подключений, перед PostgreSQL нужен PgBouncer.

Частые ошибки при выборе

  1. Выбирать по моде

    Блогу на WordPress PostgreSQL не нужна, а переезд работающей CMS ради моды только создаёт риски.

  2. Считать MariaDB той же MySQL

    MariaDB — ответвление, которое разошлось с MySQL: JSON, репликация и часть функций работают иначе.

  3. PostgreSQL без пулера

    Пик трафика открывает больше подключений, чем принимает база, и ложатся все сайты на ней.

  4. MySQL в мягком режиме

    Старые установки молча обрезают строки и принимают неверные даты. Строгий режим должен быть включён.

  5. Остаться на MySQL 8.0

    Поддержка версии 8.0 закончилась в апреле 2026 года; долгосрочная версия теперь — 8.4.

  6. Всё в JSON

    В любой из баз данным с известными правилами место в колонках с ограничениями, а не в документе.

Вопросы о PostgreSQL и MySQL

Что быстрее: PostgreSQL или MySQL?

Зависит от запросов. Для типичных сайтов обе быстрые; MySQL легче на множестве простых подключений, PostgreSQL сильнее на сложных запросах и особых индексах.

Что выбрать для нового проекта?

Если это свой код и данные будут расти — PostgreSQL. Если CMS или виртуальный хостинг — MySQL.

Может ли WordPress работать на PostgreSQL?

Официально нет: WordPress поддерживает MySQL и MariaDB. Обходные пути есть, но риск того не стоит.

Обе базы бесплатные?

Да. PostgreSQL полностью свободна; MySQL Community бесплатна по GPL, а Oracle дополнительно продаёт редакцию Enterprise.

Сложно ли переехать с MySQL на PostgreSQL?

Данные переносит pgloader; основная работа — в запросах с синтаксисом MySQL и в сравнениях с учётом регистра.

А что с MariaDB?

Это отдельная база, выросшая из MySQL. Готовые CMS с ней работают, но заменой MySQL 8 один к одному она является не везде.

Какая лучше для ИИ-поиска?

PostgreSQL с pgvector: векторы живут рядом с данными и индексируются в бесплатной версии.

Нужен ли администратор базы?

Для сайта — нет, хватит правильно настроенной базы и резервных копий. Для крупного сервиса — регулярное внимание или управляемая база в облаке.

Форма

PostgreSQL
и MySQL

Работаю с обеими базами: подбираю ту, что подходит задаче, проектирую схему, ускоряю медленные запросы и переношу данные из одной в другую. Расскажите о проекте — отвечу в течение рабочего дня.

Или пишите на [email protected]