За что я не люблю PostgreSQL

5797b048632d8bd65dbf11b1e0c69909.png

DISCLAIMER: посыл этой статьи не в том, что
«PostgreSQL — гавно, не используйте PostgreSQL».
Посыл в следующем: «Может быть я чего-то не понимаю в этой жизни? Пожалуйста, объясните, может быть я изменю своё мнение!»

Когда-то в стародавние времена (лет 20+ назад), когда выбирал для себя базовую СУБД, выбор был между MySQL и PostgreSQL, я на них обоих посмотрел и, несмотря на то, что на тот момент PostgreSQL была существенно мощнее конкурента в плане возможностей (поддержка хранимых процедур и далее по списку), я сделал для себя выбор в пользу MySQL, поскольку его SQL-синтаксис мне понравился гораздо больше.

Среди того, что понравилось в MySQL:
Гораздо более развитый DDL (Data Definition Language) и DCL (data control language): SHOW DATABASES, SHOW TABLES, SHOW CREATE TABLE и т.п.

Также приятные плюшки в DML (data modification language) типа REPLACE INTO.

За 20+ лет, в течение которых пользовался MySQL, он существенно повзрослел и возмужал, добавились:

  • поддержка хранимых процедур и функций

  • поддержка общих табличных выражений (CTE)

  • поддержка оконных функций (Window Functions)

  • репликация / кластеризация

  • поддержка различных storage engines

  • поддержка полнотекстового поиска

  • расширенная статистика для оптимизации запросов

  • поддержка JSON

  • и т.д. и т.п.

Сейчас 20 лет спустя жизнь вновь столкнула с PostgreSQL (решил помочь товарищу с его проектом) и решил дать PostgreSQL «второй шанс».
Удивлению моему не было предела, когда 20 лет спустя я столкнулся с тем, что НИЧЕГО из того, что меня напрягало ещё тогда, не улучшено.

Итак, поехали…
Для сравнения, берём последнюю версию PostgreSQL 16.

Data Control Language

MySQL

PostgreSQL

psql shortcut

SHOW DATABASES

SELECT datname FROM pg_database;

\l

SHOW TABLES

SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'

\dt

SHOW CREATE TABLE table_name

SELECT column_name, data_type, character_maximum_length, column_default, is_nullable FROM information_schema.columns WHERE table_name = 'table_name';

\d+ table_name

SHOW VARIABLES

SELECT name, setting FROM pg_settings;

\set

SHOW PROCESSLIST

SELECT * FROM pg_stat_activity;

SHOW STATUS

SELECT * FROM pg_stat_database;

SHOW GRANTS

SELECT * FROM information_schema.enabled_roles;
SELECT * FROM information_schema.role_table_grants WHERE grantee = 'rolename';

\dp

USE dbname

\c dbname

FLUSH PRIVILEGES

SELECT pg_reload_conf ();

KILL [CONNECTION | QUERY] thread_id

SELECT pg_terminate_backend (pid);

FLUSH TABLES
FLUSH HOSTS
FLUSH LOGS
FLUSH USER_RESOURCES
и т.п.

нет прямого эквивалента

Data Definition Language

MySQL

PostgreSQL

ALTER TABLE table_name CHANGE COLUMN old_column_name new_column_name new_data_type;

ALTER TABLE table_name RENAME COLUMN old_column_name TO new_column_name;
ALTER TABLE table_name ALTER COLUMN new_column_name new_data_type;

ALTER TABLE table_name ADD COLUMN col1 int AFTER col2

CREATE TABLE foobar_new (…);
INSERT INTO foobar_new SELECT … FROM foobar;
DROP TABLE foobar; ALTER TABLE foobar_new RENAME TO foobar;

можно перемещать таблицы между базами данных с помощью RENAME TABLE

Только переносить через дамп

Офигеть, я не могу добавить новую колонку после другой конкретной колонки, а могу добавить только в конец…
Можно только процитировать Тинькова: «ну это п$%дец какой-то, ну как это может быть в 21-м веке…»

Data Manipulation Language

MySQL

PostgreSQL

REPLACE INTO

INSERT INTO the_table (id, column_1, column_2) VALUES (1, 'A', 'X'), (2, 'B', 'Y'), (3, 'C', 'Z')ON CONFLICT (id) DO UPDATE SET column_1 = excluded.column_1, column_2 = excluded.column_2;

Насколько упрощает жизнь REPLACE INTO, почему это нельзя добавить?

Поддержка JSON

В PostgreSQL два отдельных типа данных для JSON: JSON и JSONB.
При этом с типом JSON нельзя сделать практически ничего, всё вкусное и полезное только в JSONB.
При этом мы имеем чрезвычайно строгий и замороченный синтаксис для работы JSON, с постоянным приведением типов туда/сюда.
Пока писал нужные мне SQL-statements для работы с JSON, пришлось мучать ChatGPT несколько часов подряд, которого я довёл, наверное, до седины — постоянно сыпались те или иные ошибки, интуитивно-очевидные конструкции с JSON не работают.
Да и у меня добавилось седых волос.

Приятно-неприятные мелочи

Удобный вывод записей:

В MySQL можно сделать удобный вывод строк в случае длинных записей / значений:

mysql> SELECT * FROM geoPoints LIMIT 1\G
*************************** 1. row ***************************
         id: 26
        lat: 53.41119462
        lon: 49.91625309
description: Родник № 2 в Каменной Чаше
    ip_addr: 85.113.61.32 
1 row in set (0.00 sec)

В PostgreSQL есть «типа аналог»: \pset format unaligned.
Однако вывод куда менее удобный для визуального анализа:

tgposts=> \pset format unaligned                      
Формат вывода: unaligned.
tgposts=> SELECT * FROM source_posts LIMIT 2;
post_id|channel_id|msg_id|post_date|grab_date|grouped_id|main_msg|post_text|post_media|media_type|media_saved
456|1124038902|51771|2024-05-28 09:29:16|2024-05-29 16:31:19||t|СДЭК намерен восстановить выдачу посылок не позднее 29 мая — заявление компании|||
457|1124038902|51772|2024-05-28 10:29:09|2024-05-29 16:31:19||t|Испания признала независимость Палестины — МИД Испании

upd: Аналогичные решения, согласно заявлениям местных властей, также официально утвердили Норвегия и Ирландия.|||
(2 строки)

Есть ещё \x, но вывод всё равно менее удобный для визуального восприятия, поскольку данные не выровнены по горизонтали:

tgposts=> \x
Расширенный вывод включён.
tgposts=> SELECT * FROM source_posts LIMIT 2;
post_id|456
channel_id|1124038902
msg_id|51771
post_date|2024-05-28 09:29:16
grab_date|2024-05-29 16:31:19
grouped_id|
main_msg|t
post_text|СДЭК намерен восстановить выдачу посылок не позднее 29 мая — заявление компании
post_media|
media_type|
media_saved|

«Безопасность должна быть безопасной»:

Почему-то в PostgreSQL по умолчанию нельзя аутентифицироваться с паролем.

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

Чтобы входить с паролем, нужно прописывать отдельные разрешения для соответствующих пользователей в pg_hba.conf и перезагружать сервер (или делать
SELECT pg_reload_conf()).

Необходимость вакуумирования:

В PostgreSQL есть необходимость периодического вакуумирования.
Как нам объясняет ChatGPT, основные причины, по которым требуется вакуумирование в PostgreSQL:

  • Очистка старых версий строк

  • Обновление статистики таблиц

  • Защита от bloat (разбухания) индексов

  • Поддержка работы MVCC MySQL все основные storage engines как-то сами решают эти проблемы без необходимости об этом отдельно беспокоиться.

И это лишь «верхушка айсберга», со сколькими ещё сюрпризами мне предстоит столкнуться?…

Почему всё так грустно и неудобно?

Спросил об этом ChatGPT:

почему в Postgres отсутствует ряд возможностей (о которых я спрашивал ранее), которые есть при этом в MySQL?

вот что он / она / оно пишет:

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

Ну хорошо, тезис со «строгой поддержкой стандартов» услышан.
Допустим, PostgreSQL так трепетно блюдёт стандарты и не добавляет ничего нестандартного…
Что же мы видим в реальности?

Видим, к примеру, ВСТРОЕННУЮ (не в виде отдельного модуля, а встроенную в core-функциональность) масштабную поддержку работы с геоданными, причём для неё определена куча отдельных пунктуационных операторов, к примеру:

SELECT geom1 && geom2; -- Возвращает true, если геометрии пересекаются
SELECT geom1 << geom2; -- Возвращает true, если geom1 полностью находится внутри geom2
SELECT geom1 >> geom2; -- Возвращает true, если geom2 полностью находится внутри geom1
SELECT geom1 &< geom2; -- Возвращает true, если геометрии пересекаются или одна находится внутри другой
SELECT geom1 &> geom2; -- Возвращает true, если геометрии пересекаются или одна содержит другую
SELECT geom1 <-> geom2; -- Возвращает расстояние между геометриями
SELECT geom1 @ geom2; -- Возвращает true, если geom1 находится внутри geom2
SELECT geom1 ~ geom2 @> distance; -- Возвращает true, если расстояние между геометриями меньше или равно distance
SELECT parallel(lseg '[(0,0),(2,2)]', lseg '[(1,1),(3,3)]'); -- Проверка параллельности отрезков, вывод: true
SELECT parallel(lseg '[(0,0),(2,2)]', lseg '[(1,0),(3,2)]'); -- Проверка параллельности отрезков, вывод: false

Хм, где же тут поддержка стандартов?
И где целесообразность и логика: как, например, быть с допусками при проверки параллельности прямых? Ведь в реальности при дискретном представлении данных прямые практически никогда не бывают параллельными, если только они не параллельны сетке координат…
Да, в PostGIS есть для этого более вменяемая функция ST_ParallelWithTolerance, но тогда зачем вообще нужна эта функция parallel в core-функционале, если она по этой причине по определению мертворожденная?

Получается, сделать поддержку SHOW TABLES или SHOW DATABASES или ADD COLUMN AFTER — религия не позволяет, а запоминать всякие однобуквенно-двухбуквенные костыли вроде \l \dt \d+ \dp или каждый раз писать портянку вроде

SELECT column_name, data_type, character_maximum_length, column_default, is_nullable
FROM information_schema.columns 
WHERE table_name = 'table_name';```

вместо SHOW CREATE TABLE или

INSERT INTO the_table (id, column_1, column_2) 
VALUES (1, 'A', 'X'), (2, 'B', 'Y'), (3, 'C', 'Z')
ON CONFLICT (id) DO UPDATE 
  SET column_1 = excluded.column_1, 
      column_2 = excluded.column_2;

вместо REPLACE INTO — это более правильный и идеологически-выверенный подход?

Тогда как вы объясните целую россыпь нестандартных пунктуационных операторов (# :: ?@ ? ?. @> <@ && -> #> и т.п.), число которых приближается к сотне и поддержку геоданных (с кучей своих операторов и встроенных функций) в ядре СУБД общего назначения?

Возможно, я просто чего-то не понимаю или просто ChatGPT мне не может адекватно объяснить. Тогда объясните мне, пожалуйста, в комментах, уважаемые читатели, буду весьма благодарен…
А то дожил до седых волос и так и не понимаю причину того, что данная СУБД, является «выбором по умолчанию» для большинства (по моим ощущениям, обгоняя в этом отношении MySQL).
Может быть все вышеперечисленные возможности не нужны или реализуются каким-то иным более удобным способом?
Просветите, пожалуйста!

© Habrahabr.ru