Серверное программирование: функции и процедуры

Рейтинг: 66.7% · 13 голосов
Подробный курс по PostgreSQL: SQL по демобазе, типы данных и JSONB, индексы, транзакции и MVCC, VACUUM, EXPLAIN и планировщик, роли и привилегии, репликация, бэкап, настройка под нагрузку и под 1С. Актуально на 2026.
Ответить
Аватара пользователя
Egor_DBA
Сообщения: 47
Зарегистрирован: 11 май 2026, 05:31

Серверное программирование: функции и процедуры

Сообщение Egor_DBA »

Оглавление курса (47)
  1. Что такое PostgreSQL: история, философия, где применяется
  2. Урок 1. Что нового в PostgreSQL 15 и далее (15 -> 16 -> 17)
  3. Установка и первый запуск: Linux, Windows, Docker
  4. psql детально: метакоманды и работа в консоли
  5. Демобаза Авиаперевозки Postgres Pro: структура и развёртывание
  6. SELECT: проекция, фильтрация, сортировка
  7. Транзакции и ACID
  8. Уровни изоляции транзакций: Read Committed, Repeatable Read, Serializable
  9. Соединения таблиц (JOIN)
  10. Агрегация: GROUP BY, HAVING, GROUPING SETS
  11. Подзапросы и CTE (WITH), рекурсия
  12. Оконные функции
  13. Изменение данных: INSERT/UPDATE/DELETE, UPSERT, MERGE
  14. Числовые и булевы типы, NULL и трёхзначная логика
  15. Строки, текст, дата и время, таймзоны
  16. Массивы, диапазоны, enum, составные типы
  17. JSON и JSONB: операторы и индексация
  18. DDL: CREATE TABLE, схемы, ALTER
  19. Ограничения целостности
  20. Представления и материализованные представления
  21. Последовательности, IDENTITY, генерируемые столбцы
  22. Индексы: B-tree и когда индекс не используется
  23. Типы индексов: Hash, GiST, SP-GiST, GIN, BRIN
  24. Продвинутые индексы: частичные, по выражению, покрывающие
  25. Обслуживание индексов и таблиц: bloat, REINDEX, CONCURRENTLY
  26. Полнотекстовый поиск
  27. Роли и пользователи
  28. Привилегии: GRANT/REVOKE
  29. Row-Level Security и безопасность данных
  30. Подключение приложения: pg_hba.conf, SSL, пулы соединений
  31. Серверное программирование: функции и процедуры (вы здесь)
  32. Триггеры и события
  33. Архитектура PostgreSQL: процессы и память
  34. MVCC: версии строк и видимость
  35. VACUUM, autovacuum, freeze и wraparound
  36. WAL, контрольные точки и долговечность
  37. Блокировки и взаимоблокировки
  38. Планировщик запросов
  39. EXPLAIN и EXPLAIN ANALYZE: чтение планов
  40. Конфигурация сервера: ключевые параметры
  41. Настройка под нагрузку и под 1С
  42. Параллельные запросы и партиционирование
  43. Резервное копирование и восстановление
  44. Репликация: потоковая и hot standby
  45. Логическая репликация и кластерные решения
  46. Внешние данные, расширения и сертификация
  47. Разбор планов запросов на explain.tensor.ru: глубокое чтение EXPLAIN ANALYZE
Урок 30. Серверное программирование: функции и процедуры

До сих пор мы гоняли запросы по одному. Но часто логику удобнее держать рядом с данными - внутри самой базы. В этом уроке разберём серверное программирование в postgresql: чем функция отличается от процедуры, как принимать и возвращать значения, как вернуть целую таблицу, зачем нужна категория волатильности (IMMUTABLE/STABLE/VOLATILE) и какие языки кроме PL/pgSQL вообще есть. Это фундамент под триггеры, которые пойдут дальше.

Изображение

Как это работает

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

PostgreSQL различает две сущности. Функция - это выражение: её вызывают внутри запроса (SELECT, WHERE, в списке колонок), она обязана вернуть значение и не управляет транзакцией. Процедура появилась в PostgreSQL 11, её вызывают отдельной командой CALL, она может ничего не возвращать и, главное, умеет управлять транзакцией внутри себя - делать COMMIT и ROLLBACK по ходу выполнения. Функцию внутри транзакции прервать на COMMIT нельзя, потому что она часть выражения.

Языков несколько. Чистый SQL-функция - тело это просто один или несколько SQL-операторов, планировщик часто умеет встроить (inline) такую функцию в запрос, и она работает быстро. PL/pgSQL - полноценный процедурный язык с переменными, IF, циклами, обработкой исключений; он идёт в коробке и покрывает 95 процентов задач. Есть и другие PL: PL/Python, PL/Perl, PL/Tcl - их подключают расширением, когда нужна логика, неудобная на SQL.

Параметры бывают трёх режимов. IN - входной (режим по умолчанию). OUT - выходной, через него функция отдаёт значение наружу. INOUT - и туда, и обратно. Несколько OUT-параметров автоматически складываются в строку-композит, так что функция может вернуть сразу несколько значений без создания типа вручную.

Категория волатильности - это обещание планировщику о поведении функции. VOLATILE (по умолчанию) - может вернуть разное при одинаковых аргументах и/или менять данные, оптимизатор её не трогает. STABLE - в пределах одного запроса при тех же аргументах результат не меняется (например, читает таблицы, но не пишет). IMMUTABLE - всегда один результат на одни аргументы, не зависит ни от чего внешнего; такую функцию можно вычислить заранее и использовать в индексе по выражению. Соврёте планировщику в категории - получите тихо неверные результаты, это классические грабли.

SQL и примеры

Простая SQL-функция. Вернём название аэропорта по коду из демобазы Авиаперевозки:

Код: Выделить всё

CREATE FUNCTION airport_name(p_code char(3))
RETURNS text
LANGUAGE sql
STABLE
AS $$
  SELECT airport_name FROM airports WHERE airport_code = p_code;
$$;

SELECT airport_name('SVO');
STABLE здесь честно: функция только читает данные. Доллары-кавычки $$ - это dollar quoting, чтобы внутри тела не экранировать одинарные кавычки.

Функция на PL/pgSQL с логикой и OUT-параметрами. Посчитаем число рейсов и среднюю стоимость билета по аэропорту вылета:

Код: Выделить всё

CREATE FUNCTION dep_stats(
  p_code char(3),
  OUT flights_cnt bigint,
  OUT avg_amount numeric
)
LANGUAGE plpgsql
STABLE
AS $$
BEGIN
  SELECT count(*), avg(tf.amount)
    INTO flights_cnt, avg_amount
  FROM flights f
  JOIN ticket_flights tf ON tf.flight_id = f.flight_id
  WHERE f.departure_airport = p_code;
END;
$$;

SELECT * FROM dep_stats('LED');
Два OUT-параметра вернулись одной строкой из двух колонок.

Возврат таблицы через RETURNS TABLE. Вернём топ рейсов по выручке для аэропорта:

Код: Выделить всё

CREATE FUNCTION top_flights(p_code char(3), p_limit int DEFAULT 5)
RETURNS TABLE(flight_no char(6), revenue numeric)
LANGUAGE sql
STABLE
AS $$
  SELECT f.flight_no, sum(tf.amount) AS revenue
  FROM flights f
  JOIN ticket_flights tf ON tf.flight_id = f.flight_id
  WHERE f.departure_airport = p_code
  GROUP BY f.flight_no
  ORDER BY revenue DESC
  LIMIT p_limit;
$$;

SELECT * FROM top_flights('SVO', 3);
RETURNS TABLE(...) - это по сути набор именованных OUT-параметров, результат - множество строк. Аналогично работает RETURNS SETOF имя_типа. У параметра p_limit есть DEFAULT, поэтому второй аргумент можно опустить.

Процедура с управлением транзакцией. Процедура - единственное место, где можно делать COMMIT внутри тела, поэтому ей удобно бить большую правку на порции:

Код: Выделить всё

CREATE PROCEDURE bump_amounts(p_step numeric)
LANGUAGE plpgsql
AS $$
BEGIN
  UPDATE ticket_flights SET amount = amount + p_step
  WHERE fare_conditions = 'Economy';
  COMMIT;
END;
$$;

CALL bump_amounts(100);
Функцию так написать нельзя - COMMIT внутри функции выдаст ошибку. Для массовых правок по условию в PostgreSQL 15 есть отдельная команда MERGE, но порционный проход с COMMIT по-прежнему делают процедурой.

Пометка по версиям: в PostgreSQL 14 процедуры научились возвращать значения через OUT-параметры (раньше только INOUT). В PostgreSQL 16 добавили SQL/JSON конструкторы, а в 17 заметно ускорили сам интерпретатор PL/pgSQL и расширили MERGE (поддержка RETURNING и обновляемых представлений).

Пример на другом PL - PL/Python (нужно расширение, ставит суперпользователь):

Код: Выделить всё

CREATE EXTENSION IF NOT EXISTS plpython3u;

CREATE FUNCTION py_upper(s text) RETURNS text
LANGUAGE plpython3u
IMMUTABLE
AS $$
  return s.upper()
$$;

SELECT py_upper('svo');
Суффикс u в plpython3u означает untrusted - такой язык имеет доступ к файловой системе, поэтому создавать функции на нём может только суперпользователь.

Частые грабли
  • Соврать в волатильности. Пометили VOLATILE-функцию как IMMUTABLE - оптимизатор закеширует результат, и вы получите устаревшие данные без единой ошибки в логе.
  • Завернуть в одинарные кавычки тело с кавычками внутри. Используйте dollar quoting $$ или $func$ - читается чище и не ломается на апострофах.
  • Ждать от функции отдельной транзакции. У функции своего COMMIT нет, она всегда внутри транзакции вызывающего. Нужен COMMIT в теле - это процедура.
  • Путать RETURN и RETURN NEXT/RETURN QUERY в set-returning функциях на PL/pgSQL. Для возврата множества строк - RETURN QUERY или RETURN NEXT в цикле.
  • Думать, что SECURITY DEFINER безопасен по умолчанию. Он выполняет функцию с правами владельца, и без явного SET search_path открывает дорогу подмене объектов.
  • Перегружать (overload) функции с DEFAULT-параметрами так, что вызов становится неоднозначным - PostgreSQL откажется выбирать и выдаст ошибку.
Мини-лаба
  • Подключитесь к демобазе: psql -d demo
  • Создайте SQL-функцию city_by_code(char(3)), возвращающую город аэропорта из airports. Пометьте STABLE и проверьте на коде 'SVO'.
  • Напишите функцию на PL/pgSQL с RETURNS TABLE, возвращающую номер рейса и число мест из seats для заданной модели самолёта (aircraft_code).
  • Создайте процедуру, которая в цикле уменьшает amount в ticket_flights на 1 процент для класса Comfort и делает COMMIT. Вызовите через CALL.
  • Сделайте функцию с двумя OUT-параметрами: минимальная и максимальная цена билета по рейсу (flight_id). Вызовите через SELECT * FROM.
  • Попробуйте создать функцию RETURNS text без RETURN в теле на PL/pgSQL - посмотрите на ошибку, затем исправьте.
  • Через \df+ имя_функции посмотрите категорию волатильности и язык каждой созданной функции.
Контрольные вопросы
  • Чем функция принципиально отличается от процедуры в плане управления транзакцией?
  • Что обещает планировщику пометка IMMUTABLE и почему такую функцию можно использовать в индексе по выражению?
  • Когда выбрать RETURNS TABLE, а когда несколько OUT-параметров?
  • Зачем нужен dollar quoting и чем он лучше одинарных кавычек?
  • В чём опасность SECURITY DEFINER без явного search_path?
  • Чем отличается trusted-язык от untrusted (суффикс u) и кто может создавать функции на untrusted PL?
👍7 ❤️3 🔥 😄 🤔2
Аватара пользователя
pg10
Сообщения: 1
Зарегистрирован: 21 май 2026, 20:59

Re: Серверное программирование: функции и процедуры

Сообщение pg10 »

А если функцию пометить STABLE, но она всё-таки иногда пишет в лог-таблицу через dblink - это считается побочкой или норм? чувствую тут засада
👍1 ❤️2 🔥 😄 🤔
Аватара пользователя
cephchan
Сообщения: 1
Зарегистрирован: 13 май 2026, 04:20

Re: Серверное программирование: функции и процедуры

Сообщение cephchan »

У меня под 1С на проце висел отчёт, переписал тяжёлую выборку в SQL-функцию со STABLE и инлайном - план стал адекватный, выборка вдвое быстрее. Реально про инлайн не знал, спасибо.
👍1 ❤️3 🔥 😄 🤔
Ответить
← Предыдущая глава
Подключение приложения: pg_hba.conf, SSL, пулы соединений
Следующая глава →
Триггеры и события

Все главы курса «PostgreSQL: от первого запроса до продакшена»

Поделиться темой: ✈ Telegram VK
Похожие запросы: триггеры и хранимые процедуры pl/pgsql в postgresql

Вернуться в «PostgreSQL: от первого запроса до продакшена»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 2 гостя