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: подробное сравнение
Четырнадцать критериев рядом. Скорость зависит от данных и запросов, поэтому в таблице — возможности и поведение, а не бенчмарки.
| Критерий | PostgreSQL | MySQL |
|---|---|---|
| Владелец и лицензия | сообщество, 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 различий, которые чувствуются в работе
Таблица — про возможности, а здесь — как они проявляются в настоящем проекте.
-
Миграции можно откатить
В PostgreSQL неудачная миграция откатывается целиком. В MySQL наполовину применённую миграцию приходится доделывать или отменять руками.
-
Один запрос вместо двух
RETURNINGсразу возвращает новую строку с её номером и значениями по умолчанию; MySQL нужен второй запрос. -
JSON без подготовки
Один индекс GIN в PostgreSQL покрывает любой ключ; в MySQL каждому ключу для поиска нужна своя вычисляемая колонка.
-
Одна база вместо трёх
Геоданные, векторы и временные ряды приходят в PostgreSQL расширениями; с MySQL это обычно отдельные системы.
-
Цена подключения
Сотни прямых подключений для MySQL дёшевы; PostgreSQL с первого дня нужен пулер перед базой.
-
Обслуживание
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 — результаты в комментариях.
Вставить или обновить
Синтаксис разный, смысл один: если товар уже есть, прибавить к остатку.
-- 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 — выбранный ключ через вычисляемую колонку.
-- 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 его уже зафиксировала.
-- 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: что учесть
Переезд частый и хорошо изученный, но несколько различий ломают код незаметно.
-
01
pgloader берёт рутину на себя
Он переносит схему и данные и сам переводит большинство типов.
-
02
Upsert переписывается
ON DUPLICATE KEY UPDATEпревращается вON CONFLICT … DO UPDATE. -
03
Кавычки вокруг имён
Обратные кавычки MySQL становятся двойными, а имена без кавычек PostgreSQL приводит к нижнему регистру.
-
04
Нулевые даты
0000-00-00в PostgreSQL не существует — такие значения до переезда превращают вNULL. -
05
Регистр и сортировка
MySQL часто сравнивает строки без учёта регистра, PostgreSQL — с учётом: поиску по почте или имени нужен
lower()илиcitext. -
06
Пулер до запуска
Коду, который открывал к MySQL сотни подключений, перед PostgreSQL нужен PgBouncer.
Частые ошибки при выборе
-
Выбирать по моде
Блогу на WordPress PostgreSQL не нужна, а переезд работающей CMS ради моды только создаёт риски.
-
Считать MariaDB той же MySQL
MariaDB — ответвление, которое разошлось с MySQL: JSON, репликация и часть функций работают иначе.
-
PostgreSQL без пулера
Пик трафика открывает больше подключений, чем принимает база, и ложатся все сайты на ней.
-
MySQL в мягком режиме
Старые установки молча обрезают строки и принимают неверные даты. Строгий режим должен быть включён.
-
Остаться на MySQL 8.0
Поддержка версии 8.0 закончилась в апреле 2026 года; долгосрочная версия теперь — 8.4.
-
Всё в 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
Работаю с обеими базами: подбираю ту, что подходит задаче, проектирую схему, ускоряю медленные запросы и переношу данные из одной в другую. Расскажите о проекте — отвечу в течение рабочего дня.