PostgreSQL vs MySQL в 2026: что выбрать для нового проекта.

Честное сравнение PostgreSQL и MySQL без религиозных войн: JSON, полнотекстовый поиск, производительность, репликация и конкретные рекомендации.

Зачем нужна база данных и почему именно эти две

Представьте, что вы открываете интернет-магазин. У вас есть товары, покупатели, заказы. Всю эту информацию нужно где-то хранить. Можно, конечно, записывать в Excel-таблицу, но когда покупателей станет тысяча, а заказов - десять тысяч, Excel начнёт тормозить, путаться и терять данные.

База данных - это специальная программа, которая хранит информацию надёжно, быстро и организованно. Она как огромный шкаф с идеально подписанными папками: вы можете мгновенно найти любую запись, добавить новую или обновить существующую.

PostgreSQL и MySQL - это две самые популярные бесплатные базы данных в мире. Обе отлично справляются со своей работой, но делают это немного по-разному. Это как Toyota и BMW - обе отличные машины, но у каждой свой характер и свои сильные стороны.

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

Как работает реляционная база данных (простыми словами)

Обе базы - "реляционные". Это значит, что данные хранятся в таблицах (как в Excel), и эти таблицы могут быть связаны между собой.

Пример: у вас есть таблица "Покупатели" и таблица "Заказы". Каждый заказ ссылается на покупателя, который его сделал. Это и есть "связь" (relation) - отсюда название "реляционная база данных".

-- SQL - это язык, на котором мы "разговариваем" с базой данных
-- Две черточки (--) в начале строки означают комментарий
-- Комментарии не выполняются, они для людей, не для компьютера

-- Создаём таблицу покупателей
-- CREATE TABLE - команда "создать таблицу"
CREATE TABLE customers (
  -- id - уникальный номер каждого покупателя (1, 2, 3, ...)
  -- Присваивается автоматически при добавлении нового покупателя
  id SERIAL PRIMARY KEY,

  -- name - имя покупателя, текст до 100 символов
  -- NOT NULL означает "обязательное поле" (нельзя оставить пустым)
  name VARCHAR(100) NOT NULL,

  -- email - электронная почта
  -- UNIQUE означает "не может повторяться" (два покупателя не могут иметь одинаковый email)
  email VARCHAR(255) UNIQUE NOT NULL,

  -- created_at - дата регистрации
  -- DEFAULT NOW() - если не указать дату, подставится текущая
  created_at TIMESTAMP DEFAULT NOW()
);

-- Создаём таблицу заказов
CREATE TABLE orders (
  id SERIAL PRIMARY KEY,

  -- customer_id - ссылка на покупателя из таблицы customers
  -- Это и есть "связь" между таблицами
  -- REFERENCES customers(id) - "это значение должно существовать в таблице customers"
  customer_id INTEGER REFERENCES customers(id),

  -- total - сумма заказа (в рублях, с копейками)
  -- NUMERIC(10, 2) - число с 10 цифрами, 2 из которых после запятой
  total NUMERIC(10, 2) NOT NULL,

  -- status - статус заказа: новый, оплачен, отправлен, доставлен
  status VARCHAR(20) DEFAULT 'new',

  created_at TIMESTAMP DEFAULT NOW()
);

-- Добавляем покупателя
-- INSERT INTO - команда "добавить запись"
INSERT INTO customers (name, email) VALUES ('Иван Петров', '[email protected]');

-- Добавляем заказ для этого покупателя
INSERT INTO orders (customer_id, total) VALUES (1, 5990.00);

-- Находим все заказы Ивана
-- SELECT - команда "найти и показать данные"
-- JOIN - "соединить" две таблицы по связи
SELECT customers.name, orders.total, orders.status
FROM orders
JOIN customers ON orders.customer_id = customers.id
WHERE customers.name = 'Иван Петров';

Этот код работает одинаково и в PostgreSQL, и в MySQL. Базовый SQL практически идентичен. Различия начинаются, когда вам нужно что-то более продвинутое.

Работа с JSON: гибкость внутри структуры

JSON - это формат хранения данных, похожий на "вложенные списки". Например, характеристики товара: у ноутбука есть процессор, оперативная память, диск, цвет - и для каждого товара набор характеристик разный.

Можно, конечно, создать отдельный столбец для каждой характеристики. Но что если у вас 500 разных товаров с разными характеристиками? У холодильника есть "объём морозилки", а у ноутбука - нет. У телефона есть "количество SIM-карт", а у телевизора - нет.

JSON решает эту проблему: вы храните произвольный набор характеристик в одном столбце.

PostgreSQL имеет тип jsonb (JSON Binary) - это настоящая суперспособность Postgres:

-- PostgreSQL: JSONB - мощная работа с JSON-данными

-- Создаём таблицу товаров с колонкой для характеристик
CREATE TABLE products (
  id SERIAL PRIMARY KEY,
  name TEXT NOT NULL,
  price NUMERIC(10, 2) NOT NULL,

  -- attrs - столбец типа JSONB
  -- Сюда можно положить любой набор характеристик
  -- DEFAULT '{}' - по умолчанию пустой JSON-объект
  attrs JSONB DEFAULT '{}'
);

-- Добавляем ноутбук с его характеристиками
INSERT INTO products (name, price, attrs) VALUES (
  'MacBook Pro 16',
  249990.00,
  -- Это JSON: набор "ключ: значение"
  -- Обратите внимание: внутри JSON можно хранить текст, числа, массивы
  '{
    "cpu": "M3 Max",
    "ram": 36,
    "storage": "1TB SSD",
    "color": "Space Black",
    "ports": ["HDMI", "USB-C", "MagSafe"]
  }'
);

-- Добавляем холодильник - совсем другие характеристики!
INSERT INTO products (name, price, attrs) VALUES (
  'Samsung RT35',
  54990.00,
  '{
    "type": "двухкамерный",
    "volume_fridge": 255,
    "volume_freezer": 76,
    "energy_class": "A+",
    "color": "белый"
  }'
);

-- GIN-индекс - ускоряет поиск по ЛЮБОМУ ключу в JSON
-- Без этого индекса каждый поиск будет перебирать ВСЕ записи в таблице
-- С индексом - поиск мгновенный даже на миллионах записей
CREATE INDEX idx_products_attrs ON products USING GIN (attrs);

-- Теперь ищем все товары с процессором M3 Max
-- Оператор @> означает "содержит" (левый JSON содержит правый)
-- Этот запрос использует GIN-индекс и работает быстро!
SELECT * FROM products WHERE attrs @> '{"cpu": "M3 Max"}';

-- Извлекаем конкретное значение из JSON
-- ->> означает "достать значение как текст"
-- -> достаёт значение как JSON-объект (для вложенных структур)
SELECT
  name,
  price,
  attrs->>'cpu' AS processor,       -- "M3 Max" (текст)
  (attrs->>'ram')::INTEGER AS ram_gb -- 36 (число, ::INTEGER - преобразование)
FROM products
WHERE attrs ? 'cpu';  -- Только те товары, где ЕСТЬ ключ "cpu"

-- Обновляем одно поле в JSON, не перезаписывая весь объект
-- jsonb_set(json, путь, новое_значение)
-- Это очень удобно - не нужно читать весь JSON, менять и записывать обратно
UPDATE products
SET attrs = jsonb_set(attrs, '{ram}', '64')
WHERE name = 'MacBook Pro 16';

-- Добавляем новое поле в JSON
UPDATE products
SET attrs = attrs || '{"wifi": "Wi-Fi 6E"}'
WHERE name = 'MacBook Pro 16';
-- Оператор || объединяет два JSON-объекта

MySQL тоже поддерживает JSON, но без бинарного формата и с ограниченной индексацией:

-- MySQL: JSON - работает, но менее удобно

CREATE TABLE products (
  id INT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(255) NOT NULL,
  price DECIMAL(10, 2) NOT NULL,

  -- В MySQL тип JSON тоже есть, но он хранится как текст
  attrs JSON DEFAULT NULL
);

INSERT INTO products (name, price, attrs) VALUES (
  'MacBook Pro 16',
  249990.00,
  '{"cpu": "M3 Max", "ram": 36, "storage": "1TB SSD"}'
);

-- В MySQL НЕЛЬЗЯ создать индекс на весь JSON целиком
-- Вместо этого нужно создать "виртуальный столбец" для каждого ключа,
-- по которому хотите искать
ALTER TABLE products
  ADD COLUMN cpu VARCHAR(50)
  GENERATED ALWAYS AS (JSON_UNQUOTE(attrs->'$.cpu')) STORED;

-- Теперь создаём обычный индекс на виртуальный столбец
CREATE INDEX idx_cpu ON products(cpu);

-- Поиск работает быстро, но ТОЛЬКО по столбцу cpu
SELECT * FROM products WHERE cpu = 'M3 Max';

-- Для поиска по другим ключам нужно создать ещё виртуальные столбцы
-- Это неудобно, если ключей много или они непредсказуемые

-- Извлечение значения (другой синтаксис)
SELECT
  name,
  JSON_UNQUOTE(attrs->'$.cpu') AS processor,
  JSON_EXTRACT(attrs, '$.ram') AS ram_gb
FROM products;

Вывод: если ваш проект активно работает с JSON-данными (каталоги товаров, настройки, гибкие атрибуты) - PostgreSQL с JSONB удобнее и быстрее. Если JSON нужен только для хранения и вы не делаете сложных поисков внутри него - MySQL справится.

Полнотекстовый поиск: ищем по словам

Полнотекстовый поиск - это когда вы ищете не по точному совпадению, а по смыслу. Например, вводите "оптимизация сайта" и находите статьи, где встречаются слова "оптимизировать", "оптимальный", "сайт", "веб-сайт" и так далее.

Обе базы умеют это, но по-разному:

-- PostgreSQL: мощный полнотекстовый поиск с весами и ранжированием

-- Добавляем специальный столбец для поиска
-- tsvector - это "словарь" из слов документа (токены)
ALTER TABLE articles ADD COLUMN search_vector tsvector;

-- Заполняем поисковый вектор
-- setweight - назначает "вес" (важность) словам из разных полей
-- 'A' - самый важный (заголовок), 'B' - менее важный (текст)
-- to_tsvector('russian', ...) - разбивает текст на слова с учётом русского языка
-- (убирает окончания, предлоги и т.д.)
UPDATE articles SET search_vector =
  setweight(to_tsvector('russian', title), 'A') ||
  setweight(to_tsvector('russian', content), 'B');

-- Создаём индекс для быстрого поиска
CREATE INDEX idx_search ON articles USING GIN (search_vector);

-- Ищем статьи про оптимизацию производительности
-- to_tsquery - превращает запрос в поисковый формат
-- & означает "И" (оба слова должны быть)
-- | означает "ИЛИ" (любое из слов)
-- ts_rank - оценивает "релевантность" (насколько хорошо документ подходит)
SELECT
  title,
  ts_rank(search_vector, query) AS relevance
FROM articles,
     to_tsquery('russian', 'оптимизация & производительность') query
WHERE search_vector @@ query
ORDER BY relevance DESC;

-- PostgreSQL найдёт статьи со словами:
-- "оптимизация", "оптимизировать", "оптимизационный"
-- "производительность", "производительный"
-- Потому что он понимает русские словоформы!
-- MySQL: полнотекстовый поиск проще в настройке

-- Создаём FULLTEXT-индекс
ALTER TABLE articles ADD FULLTEXT INDEX ft_idx (title, content);

-- Ищем в "натуральном" режиме (MySQL сам определяет релевантность)
-- MATCH ... AGAINST - функция полнотекстового поиска MySQL
SELECT
  title,
  MATCH(title, content) AGAINST('оптимизация производительность') AS score
FROM articles
WHERE MATCH(title, content) AGAINST('оптимизация производительность')
ORDER BY score DESC;

-- Булев режим - точнее, но нет ранжирования по релевантности
-- + означает "слово обязательно должно быть"
-- - означает "слово обязательно НЕ должно быть"
SELECT title FROM articles
WHERE MATCH(title, content)
AGAINST('+оптимизация -wordpress' IN BOOLEAN MODE);
-- Найдёт статьи с "оптимизация", но без "wordpress"

PostgreSQL даёт больше контроля: веса полей, языковые словари, настройка стемминга (приведение слов к корню). MySQL проще настроить, но менее гибок. Для серьёзного поиска по сайту обе базы проигрывают специализированным решениям вроде Elasticsearch - но для небольших проектов встроенного поиска достаточно.

Производительность: кто быстрее

Сравнивать производительность баз данных - дело непростое, потому что результат зависит от настроек, оборудования и типа нагрузки. Но общие закономерности есть:

Простые операции (добавить запись, найти по ID, обновить) - MySQL чуть быстрее, примерно на 5-15%. Его "движок" InnoDB оптимизирован именно для таких операций. Если ваше приложение в основном читает и записывает отдельные записи - MySQL будет немного шустрее.

Сложные аналитические запросы (объединение таблиц, группировка, подзапросы) - PostgreSQL выигрывает за счёт более умного "планировщика запросов". Планировщик - это часть базы данных, которая решает, как лучше выполнить ваш запрос. PostgreSQL лучше оптимизирует сложные запросы, и разница может быть в 2-5 раз.

Много пользователей одновременно пишут данные - PostgreSQL стабильнее. Обе базы используют технологию MVCC (Multi-Version Concurrency Control - "контроль многоверсионного доступа"), но PostgreSQL справляется с параллельной записью лучше при высокой нагрузке.

Очень большие таблицы (больше 100 миллионов строк) - PostgreSQL лучше справляется с разделением таблиц на части (партиционирование) и параллельным выполнением запросов.

Репликация: копии данных для надёжности

Репликация - это создание копий вашей базы данных на нескольких серверах. Зачем? Две причины:

  • Надёжность - если один сервер сломается, данные не пропадут (есть копия)
  • Скорость - запросы на чтение можно распределить между копиями

Аналогия: представьте библиотеку. Если в ней одна копия книги - все стоят в очереди. Если сделать 3 копии и поставить в разных залах - очередей нет, все читают параллельно. Это и есть репликация.

-- PostgreSQL: потоковая репликация (WAL-based)
-- WAL = Write-Ahead Log - "журнал изменений"
-- Основной сервер записывает все изменения в журнал
-- Копии (реплики) читают этот журнал и повторяют изменения у себя

-- Шаг 1: Настроить основной сервер (master/primary)
-- В файле postgresql.conf:
-- wal_level = replica       (включаем потоковую репликацию)
-- max_wal_senders = 3       (максимум 3 реплики)

-- Шаг 2: Создать пользователя для репликации
CREATE ROLE replicator WITH REPLICATION LOGIN PASSWORD 'secure_password';

-- Шаг 3: На реплике - скопировать данные с основного сервера
-- pg_basebackup -h primary-host -D /var/lib/postgresql/data -U replicator

-- После этого реплика автоматически получает все изменения
-- и содержит точную копию данных основного сервера
-- MySQL: бинарная репликация (binlog-based)
-- Binlog = Binary Log - "бинарный журнал"
-- Принцип тот же: основной сервер записывает изменения в журнал

-- Шаг 1: Настроить основной сервер
-- В файле my.cnf:
-- server-id = 1             (уникальный ID сервера)
-- log_bin = mysql-bin       (включаем бинарный журнал)

-- Шаг 2: Создать пользователя для репликации
CREATE USER 'replicator'@'%' IDENTIFIED BY 'secure_password';
GRANT REPLICATION SLAVE ON *.* TO 'replicator'@'%';

-- Шаг 3: На реплике - указать, откуда брать данные
CHANGE REPLICATION SOURCE TO
  SOURCE_HOST='primary-host',
  SOURCE_USER='replicator',
  SOURCE_PASSWORD='secure_password';
START REPLICA;

Обе системы отлично справляются с репликацией. MySQL исторически проще в настройке - меньше шагов, меньше конфигурации. PostgreSQL даёт больше контроля и гарантий целостности данных.

Экосистема и хостинг

MySQL исторически доступнее: каждый хостинг предлагает MySQL, WordPress работает на MySQL, большинство готовых систем управления сайтами (CMS) - тоже. Если вы делаете сайт на PHP с WordPress, Bitrix или Laravel - MySQL будет самым простым выбором.

PostgreSQL набрал огромную популярность в последние годы. Все современные облачные платформы предлагают управляемый PostgreSQL: Supabase, Neon, Railway, Render. В мире Node.js и TypeScript PostgreSQL стал стандартом: Prisma, Drizzle, TypeORM и другие ORM-библиотеки прекрасно работают с Postgres.

Уникальные фичи каждой базы

PostgreSQL - что есть только в нём:

  • JSONB с GIN-индексами - полноценная работа с JSON-документами внутри реляционной базы
  • PostGIS - работа с географическими данными (координаты, расстояния, "найти все кафе в радиусе 1 км")
  • Массивы как тип данных - можно хранить список значений в одном поле (INTEGER[], TEXT[])
  • Диапазоны (daterange, int4range) - удобно для бронирований ("занято с 10:00 до 14:00")
  • LISTEN/NOTIFY - встроенная система уведомлений (сервер может сообщить клиенту об изменениях)
  • Расширения: pgvector (поиск по AI-эмбеддингам), TimescaleDB (временные ряды), Citus (горизонтальное масштабирование)

MySQL - в чём он сильнее:

  • Простота настройки и администрирования - меньше настроек, проще начать
  • Меньше потребление памяти при одинаковой нагрузке
  • Быстрее на простых CRUD-операциях (создать, прочитать, обновить, удалить)
  • Огромная база знаний - любая проблема уже решена кем-то на Stack Overflow
  • Лучшая совместимость с PHP-экосистемой (WordPress, Laravel, Magento, Bitrix)
  • Group Replication и InnoDB Cluster - проще настроить отказоустойчивый кластер

Что выбрать: конкретные рекомендации

Выбирайте PostgreSQL, если:

  • Проект на Node.js, TypeScript, Python или Go
  • Нужна работа с JSON-данными (каталоги товаров, настройки, гибкие атрибуты)
  • Есть геоданные (карты, поиск по координатам, "ближайшие точки")
  • Сложная аналитика (отчёты, дашборды, группировки)
  • Планируете использовать AI и векторный поиск
  • Проект будет расти до больших объёмов данных

Выбирайте MySQL, если:

  • Проект на PHP (WordPress, Laravel, Bitrix, Magento)
  • Нужен обычный хостинг (shared hosting)
  • Простое приложение без сложной аналитики
  • Команда хорошо знает MySQL и не хочет переучиваться
  • Нужна максимальная скорость на простых запросах

Миграция: если решили переехать

Если вы уже используете одну базу и хотите перейти на другую - вот основные различия в синтаксисе, которые придётся учесть:

-- Автоматический счётчик (ID)
-- MySQL: AUTO_INCREMENT
-- CREATE TABLE users (id INT AUTO_INCREMENT PRIMARY KEY, ...);
-- PostgreSQL: SERIAL или GENERATED ALWAYS AS IDENTITY
-- CREATE TABLE users (id SERIAL PRIMARY KEY, ...);

-- Кавычки для имён таблиц и столбцов
-- MySQL использует обратные кавычки: `table_name`
-- PostgreSQL использует двойные кавычки: "table_name"

-- Типы данных
-- MySQL: TINYINT(1) для true/false, DATETIME для даты
-- PostgreSQL: BOOLEAN для true/false, TIMESTAMP WITH TIME ZONE для даты

-- Объединение строк
-- MySQL: CONCAT('привет', ' ', 'мир')
-- PostgreSQL: 'привет' || ' ' || 'мир'

-- Вставка с обработкой конфликтов (upsert)
-- MySQL: INSERT ... ON DUPLICATE KEY UPDATE ...
-- PostgreSQL: INSERT ... ON CONFLICT DO UPDATE ...

-- Ограничение результатов
-- MySQL: LIMIT 10, 20  (пропустить 10, взять 20)
-- PostgreSQL: LIMIT 20 OFFSET 10  (взять 20, пропустив 10)

-- Регистр символов в именах
-- MySQL: имена таблиц могут зависеть от регистра (зависит от ОС)
-- PostgreSQL: все имена приводятся к нижнему регистру (если не в кавычках)

Для миграции существуют специальные инструменты: pgloader (автоматически переносит данные из MySQL в PostgreSQL), AWS DMS (для облачных баз данных). Если вы используете ORM (Prisma, Drizzle, TypeORM), миграция проще - меняете строку подключения и пересоздаёте миграции.

Итого

В 2026 году PostgreSQL - лидер для новых проектов в экосистеме Node.js, Python и Go. Он мощнее, гибче и лучше подходит для современных задач: JSON, геоданные, аналитика, AI. MySQL остаётся отличным выбором для PHP-проектов, простых приложений и ситуаций, когда команда хорошо его знает.

Обе базы надёжны, масштабируемы и проверены десятилетиями. Неправильный выбор между ними - это не катастрофа, а небольшое неудобство. Настоящая катастрофа - это не делать бэкапы, не настраивать индексы и не думать о структуре данных. Если нужна помощь с выбором или настройкой базы данных для вашего проекта - напишите нам, подберём оптимальное решение.

Все статьи
PostgreSQL vs MySQL в 2026: что выбрать для нового проекта | Enot Software