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; |
\dp |
|
USE dbname |
|
\c dbname |
|
FLUSH PRIVILEGES |
SELECT pg_reload_conf(); |
|
|
KILL [CONNECTION | QUERY] thread_id |
SELECT pg_terminate_backend(pid); |
|
|
FLUSH TABLES |
нет прямого эквивалента |
|
Сразу ответ на коммент:
В постгре мета-информация хранится в табличках, что очень гибко, ибо позволяет использовать всю мощь SQL для работы с этими данными, а не зазубривать ограниченные кастомные команды
В MySQL также давно появилась служебная базаinformation_schema, из которой также можно извлечь всё что нужно. При этом остаются удобные и легко-запоминающиеся шорткаты SHOW, для тех, кто хочет быстро что-то посмотреть.
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 ADD COLUMN col1 int AFTER col2 |
CREATE TABLE foobar_new ( … ); |
|
можно перемещать таблицы между базами данных с помощью 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’) |
Насколько упрощает жизнь 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).
Может быть все вышеперечисленные возможности не нужны или реализуются каким-то иным более удобным способом?
Просветите, пожалуйста!
ссылка на оригинал статьи https://habr.com/ru/articles/819177/
Добавить комментарий