Команда Go for Devs подготовила перевод обзора PostgreSQL 18. Новый релиз не ограничился громкой подсистемой асинхронного ввода-вывода — он принёс ряд функций, заметных именно в повседневной разработке. Нативная поддержка UUID v7, виртуальные генерируемые столбцы, расширенные возможности RETURNING и новые средства диагностики делают жизнь разработчиков проще и продуктивнее.


PostgreSQL 18 официально вышел 25 сентября 2025 года. Несомненно, самым значимым изменением стала новая подсистема асинхронного ввода-вывода (AIO). Однако в этой статье основное внимание уделено функциям, которые повлияют на повседневную работу разработчиков, начиная с нативной поддержки UUID v7.

Нативная поддержка UUID v7

PostgreSQL 18 представляет функцию uuidv7() для генерации значений UUID версии 7. Эта нативная поддержка практически завершает давние споры о том, использовать ли типы SERIAL/IDENTITY или UUID в качестве первичных ключей. UUIDv7 объединяет лучшее из обоих миров: глобальную уникальность UUID с последовательной природой, что обеспечивает высокую производительность serial ключей.

-- Сгенерировать UUIDv7
SELECT uuidv7();

-- Создать таблицу, используя UUIDv7 в качестве первичного ключа
CREATE TABLE orders (
    id UUID PRIMARY KEY DEFAULT uuidv7(),
    customer_id INT,
    total DECIMAL(10,2),
    created_at TIMESTAMP DEFAULT NOW()
);

Хотя расширения для PostgreSQL могли генерировать UUIDv7, наиболее широко используемое расширение uuid-ossp не реализует седьмую версию UUID. Менее известные расширения, которые поддерживают v7, обычно не поставляются с дистрибутивами PostgreSQL, особенно у облачных провайдеров. Это означало, что разработчикам, желающим использовать UUIDv7, приходилось реализовывать логику генерации в своих приложениях. Нативная поддержка полностью устраняет это препятствие.

Виртуальные генерируемые столбцы

Генерируемые столбцы впервые появились в PostgreSQL 12, но только с параметром STORED, что означало, что сгенерированный столбец хранится при записи и занимает место на диске. Добавление генерируемого столбца STORED приводило к перезаписи всей таблицы.

PostgreSQL 18 представляет параметр VIRTUAL и делает его поведением по умолчанию для генерируемых столбцов. Эти столбцы вычисляют свои значения при чтении, а не при записи. Это более распространенный подход, и то, что он стал по умолчанию, приводит PostgreSQL в соответствие с другими основными базами данных.

-- Create table with virtual generated column
CREATE TABLE employees (
    id SERIAL PRIMARY KEY,
    first_name TEXT NOT NULL,
    last_name TEXT NOT NULL,
    salary DECIMAL(10,2),
    -- You can also skip VIRTUAL since it's the default option
    annual_salary DECIMAL(12,2) GENERATED ALWAYS AS (salary * 12) VIRTUAL
);

-- Insert data (generated columns are computed automatically)
INSERT INTO employees (first_name, last_name, salary) VALUES
    ('John', 'Doe', 5000.00),
    ('Jane', 'Smith', 6500.00);

SELECT first_name, last_name, salary, annual_salary
FROM employees;

Преимущества виртуальных столбцов:

  • Экономия места: для производных данных, которым не требуется физическое хранение, виртуальные столбцы исключают избыточное использование диска.

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

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

Однако существуют определённые ограничения, как указано в коммите. Наиболее заметное из них заключается в том, что нельзя создавать индексы на генерируемых столбцах типа VIRTUAL.

Кстати, PostgreSQL 18 также добавляет поддержку генерируемых столбцов в логической репликации (коммит, документация). Эта функция доступна только для генерируемых столбцов типа STORED.

Старые и новые значения в RETURNING

PostgreSQL 18 улучшает выражение RETURNING, позволяя получать доступ как к старым, так и к новым значениям строк в DML-операциях. Это устраняет необходимость в отдельных запросах для захвата значений до/после в журналах аудита.

Операции UPDATE

-- Обновить цены и увидеть как старые, так и новые значения
UPDATE products
SET price = price * 1.10
WHERE price <= 99.99
RETURNING
    name,
    old.price AS old_price,
    new.price AS new_price,
    new.price - old.price AS price_change;

Операция INSERT с ON CONFLICT

-- Upsert с отслеживанием изменений
INSERT INTO products (name, price) VALUES ('Widget', 25.00)
ON CONFLICT (name) DO UPDATE SET price = EXCLUDED.price
RETURNING
    name,
    old.price AS previous_price,
    new.price AS current_price,
    (old.price IS NULL) AS is_new_record;

Операции DELETE

-- Отслеживать, что было удалено
DELETE FROM products
WHERE price < 10.00
RETURNING
    old.name AS deleted_product,
    old.price AS deleted_price;

EXPLAIN ANALYZE с BUFFERS по умолчанию

PostgreSQL 18 улучшает команду EXPLAIN для автоматического предоставления более подробной информации о выполнении запросов.

Теперь EXPLAIN ANALYZE по умолчанию включает информацию об использовании буферов, что устраняет необходимость в параметре BUFFERS. Такое поведение по умолчанию помогает разработчикам выявлять проблемы с производительностью ввода-вывода, которые в противном случае остались бы незамеченными:

postgres=# EXPLAIN ANALYZE SELECT * FROM users WHERE age > 25;

-- PostgreSQL 18: BUFFERS включены автоматически
 Seq Scan on users  (cost=0.00..18.75 rows=125 width=64) (actual time=0.029..0.087 rows=178 loops=1)
   Filter: (age > 25)
   Rows Removed by Filter: 89
   Buffers: shared hit=12
 Planning:
   Buffers: shared hit=156 read=3
   I/O Timings: shared read=0.024
 Planning Time: 0.512 ms
 Execution Time: 0.734 ms

pg_get_acl() для проверки привилегий

PostgreSQL 18 представляет функцию pg_get_acl() для программного получения списков контроля доступа (ACL) для объектов базы данных. Если вы когда-либо тратили время на отладку печально известных сообщений ERROR 42501: permission denied, вы оцените наличие унифицированного способа проверки привилегий объектов.

Ранее для устранения проблем с разрешениями требовалось запрашивать различные системные каталоги (pg_class, pg_proc, pg_namespace, pg_attribute) в зависимости от типа объекта, каждый из которых имел свой формат ACL.

Функция pg_get_acl() предоставляет унифицированный интерфейс для получения ACL из любого объекта базы данных, устраняя необходимость запоминать, какой каталог запрашивать для разных типов объектов.

postgres=# SELECT
    (pg_identify_object(s.classid,s.objid,s.objsubid)).*,
    pg_catalog.pg_get_acl(s.classid,s.objid,s.objsubid) AS acl
FROM pg_catalog.pg_shdepend AS s
JOIN pg_catalog.pg_database AS d
    ON d.datname = current_database() AND
       d.oid = s.dbid
JOIN pg_catalog.pg_authid AS a
    ON a.oid = s.refobjid AND
       s.refclassid = 'pg_authid'::regclass
WHERE s.deptype = 'a';
-[ RECORD 1 ]-----------------------------------------
type     | table
schema   | public
name     | testtab
identity | public.testtab
acl      | {postgres=arwdDxtm/postgres,foo=r/postgres}

Хотя эти улучшения не так сенсационны, как новый AIO, они упрощают повседневную разработку — а более понятные интерфейсы, такие как pg_get_acl(), приносят пользу не только разработчикам-людям, но и ИИ-агентам.

Русскоязычное Go сообщество

Друзья! Эту статью подготовила команда «Go for Devs» — сообщества, где мы делимся практическими кейсами, инструментами для разработчиков и свежими новостями из мира Go. Подписывайтесь, чтобы быть в курсе и ничего не упустить!

Комментарии (1)


  1. VladimirFarshatov
    30.09.2025 07:31

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