Хранимые процедуры SQL — именованные наборы SQL-команд и управляющей логики, сохранённые на сервере базы данных. Создаются один раз, вызываются командой CALL или EXEC с нужными параметрами. Три ключевых свойства: хранятся на сервере, а не в коде приложения; компилируются при создании, план выполнения кэшируется для повторных вызовов; принимают параметры трёх типов — IN, OUT и INOUT. Один вызов CALL заменяет многократную отправку запросов по сети — это и делает процедуры основным инструментом для сложной бизнес-логики в реляционной базе данных.
Хранимая процедура хранится непосредственно на сервере базы данных — в отличие от обычного SQL-запроса, который приложение отправляет по сети при каждом обращении. При выполнении команды CREATE PROCEDURE сервер разбирает код, строит оптимальный план выполнения и сохраняет его в кэше. При следующем вызове повторная компиляция не нужна: СУБД использует уже готовый план, что ускоряет работу при многократных обращениях.
Учитесь бесплатно за счёт государства
Экономия до 100 000 ₽ на любой программе
Структура хранимой процедуры состоит из четырёх частей:
Хранимую процедуру легко спутать с функцией или триггером, но ключевое отличие уже в вызове: процедуру запускают явно командой CALL или EXEC, функция встраивается в тело запроса SELECT, а триггер срабатывает автоматически при изменении данных в таблице.

Хранимые процедуры решают несколько задач одновременно: ускоряют повторные запросы, снижают сетевой трафик и защищают данные от несанкционированного доступа. При этом у них есть реальные ограничения, которые важно учитывать при проектировании системы.
| Преимущество |
Пояснение |
Ограничение |
Пояснение |
|---|---|---|---|
| Кэш плана выполнения | Повторная компиляция не нужна — сервер использует готовый план | Привязка к конкретной СУБД | Перенос кода между PostgreSQL и SQL Server требует переписывания |
| Снижение сетевого трафика | Один вызов CALL вместо десятков отдельных запросов | Сложность отладки | Нет единого стандарта отладчиков — инструменты у каждой СУБД свои |
| Инкапсуляция бизнес-логики | Логика в базе данных, а не в каждом клиентском приложении | Версионирование | Нет встроенного контроля версий — нужны внешние инструменты |
| Безопасность через GRANT EXECUTE | Пользователь запускает процедуру без прямого доступа к таблицам | Сложность рефакторинга | Изменение затрагивает сразу всех клиентов, использующих процедуру |
| Переиспользование кода | Одна процедура обслуживает Web, API и мобильный клиент | Трудно тестировать | Юнит-тесты сложнее, чем для кода приложения |
Кэш плана — главный источник прироста производительности: СУБД не разбирает и не оптимизирует запрос заново при каждом вызове. GRANT EXECUTE позволяет предоставить право на запуск без прямого доступа к самим таблицам — это снижает риск случайного или намеренного повреждения данных.
Синтаксис хранимых процедур существенно различается в зависимости от системы управления базами данных. Общая логика одна: заголовок с именем и параметрами, тело между BEGIN и END, механизм обработки ошибок. Но команды создания, разделители, префиксы параметров и способ изменения готовой процедуры у каждой СУБД свои.
| СУБД |
Создание |
Вызов |
Механизм ошибок |
Изменение |
|---|---|---|---|---|
| PostgreSQL | CREATE OR REPLACE PROCEDURE | CALL name() | EXCEPTION WHEN | CREATE OR REPLACE |
| MySQL | CREATE PROCEDURE | CALL name() | DECLARE EXIT HANDLER | DROP + CREATE |
| SQL Server | CREATE / ALTER PROCEDURE | EXEC / EXECUTE | TRY / CATCH | ALTER PROCEDURE |
| Oracle | CREATE OR REPLACE PROCEDURE | EXECUTE name | EXCEPTION WHEN OTHERS | CREATE OR REPLACE |
Если работа с базами данных — направление, в котором вы хотите развиваться, в рамках федерального проекта «Активные меры содействия занятости» нацпроекта «Кадры» можно пройти программу «Специалист по аналитике и базам данных в информационных системах» — бесплатно, онлайн, с нуля. Подробности — в каталоге программ обучения.
PostgreSQL использует процедурный язык PL/pgSQL — расширение SQL с переменными, условиями, циклами и обработкой исключений. Тело процедуры заключается в долларовое квотирование: $$ … $$. Это избавляет от необходимости экранировать одинарные кавычки внутри кода.
CREATE OR REPLACE PROCEDURE transfer_funds(
p_from INT, p_to INT, p_amount NUMERIC
)
LANGUAGE plpgsql AS $$
DECLARE
v_balance NUMERIC;
BEGIN
SELECT balance INTO v_balance FROM accounts WHERE id = p_from;
IF v_balance < p_amount THEN
RAISE EXCEPTION ‘Недостаточно средств’;
END IF;
UPDATE accounts SET balance = balance — p_amount WHERE id = p_from;
UPDATE accounts SET balance = balance + p_amount WHERE id = p_to;
COMMIT;
END;
$$;
Вызов: CALL transfer_funds(1, 2, 500.00). PostgreSQL поддерживает перегрузку процедур — несколько процедур с одним именем, но разными параметрами. DEFAULT-значения и именованный вызов: CALL search_products(p_category => ‘books’) — аргумент передаётся по имени, остальные принимают значения по умолчанию.
В MySQL перед созданием процедуры обязательно менять разделитель командой DELIMITER. Без этого сервер завершит команду на первой точке с запятой внутри тела — процедура не создастся.
DELIMITER //
CREATE PROCEDURE get_order(IN p_id INT)
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
RESIGNAL;
END;
START TRANSACTION;
SELECT * FROM orders WHERE id = p_id;
COMMIT;
END//
DELIMITER ;
Вызов: CALL get_order(42). Изменить тело существующей процедуры в MySQL нельзя — только удалить и создать заново: ALTER PROCEDURE изменяет лишь метаданные (комментарий, характеристики безопасности), но не тело.
В SQL Server хранимые процедуры пишутся на языке Transact-SQL (T-SQL). Все параметры и локальные переменные начинаются с символа @. Это обязательное правило MS SQL — в PostgreSQL и MySQL такой префикс не используется.
CREATE PROCEDURE usp_get_customer
@customer_id INT,
@include_orders BIT = 0
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
SELECT * FROM customers WHERE id = @customer_id;
IF @include_orders = 1
SELECT * FROM orders WHERE customer_id = @customer_id;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
THROW;
END CATCH
END;
Вызов с именованными параметрами: EXEC usp_get_customer @customer_id = 5, @include_orders = 1. Для изменения используется ALTER PROCEDURE без удаления — это уникальная возможность среди рассмотренных СУБД. Безопасный динамический SQL реализуется через sp_executesql с параметрами вместо конкатенации строк.
Хотите сменить профессию или повысить квалификацию?
Федеральный проект «Активные меры содействия занятости» даёт возможность пройти обучение бесплатно за счёт государства
Oracle использует процедурный язык PL/SQL. Ссылочный тип table.column%TYPE позволяет объявить переменную с типом конкретного столбца — процедура не сломается при изменении схемы таблицы.
CREATE OR REPLACE PROCEDURE get_employee(p_id IN employees.id%TYPE)
AS
v_name employees.name%TYPE;
BEGIN
SELECT name INTO v_name FROM employees WHERE id = p_id;
DBMS_OUTPUT.PUT_LINE(v_name);
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE_APPLICATION_ERROR(-20001, ‘Сотрудник не найден’);
END get_employee;
/
Oracle компилирует процедуры в байт-код — это ускоряет повторное выполнение. Пакеты (Packages) группируют связанные процедуры, функции и типы в один объект схемы. PRAGMA AUTONOMOUS_TRANSACTION позволяет выполнять процедуру в независимой транзакции, не затрагивающей вызывающую.
Параметры управляют потоком данных между вызывающим кодом и процедурой. Если режим не указан явно, параметр считается IN — значение передаётся в процедуру, но вызывающий код не получает изменений обратно.
| Тип |
Направление |
Поведение |
Типичный пример |
|---|---|---|---|
| IN | Снаружи → процедура | Только чтение внутри | Фильтр по ID, сумма платежа |
| OUT | Процедура → снаружи | Процедура записывает значение, вызывающий код считывает | Возврат имени, итоговой суммы |
| INOUT | Снаружи ↔ процедура | Передаёт значение и возвращает изменённое | Расчёт скидки к исходной цене |
IN-параметр — самый распространённый: передаёт значение (идентификатор, категорию, сумму), процедура читает его, но значение в вызывающем контексте не меняется.
OUT-параметр возвращает результат через аргумент. В PostgreSQL: CALL get_student_name(1, NULL) — второй аргумент при вызове равен NULL, после выполнения содержит имя студента.
INOUT передаёт значение и возвращает изменённое: процедура расчёта скидки получает цену, уменьшает её на нужный процент и записывает результат обратно в тот же параметр.
В PostgreSQL DEFAULT-значения позволяют не передавать все аргументы: CALL search_products(p_category => ‘books’) — аргумент указывается по имени, остальные принимают значения по умолчанию.
Управляющая логика — то, что принципиально отличает хранимые процедуры SQL от обычных запросов. Процедура может содержать переменные, ветвления, циклы и блоки обработки ошибок.
Переменные. В PostgreSQL: DECLARE v_counter INT := 0;. В SQL Server: DECLARE @counter INT = 0;. В Oracle можно использовать ссылочный тип: v_price products.price%TYPE — переменная автоматически получит тип столбца.
Условия. PostgreSQL и Oracle: IF … ELSIF … ELSE … END IF. SQL Server: IF … BEGIN … END ELSE IF … BEGIN … END. MySQL: IF … ELSEIF … END IF.
Циклы. PostgreSQL: LOOP … EXIT WHEN condition; END LOOP или FOR i IN 1..10 LOOP … END LOOP. SQL Server: WHILE condition BEGIN … END. MySQL: WHILE condition DO … END WHILE.
Обработка ошибок. PostgreSQL: EXCEPTION WHEN division_by_zero THEN …. SQL Server: BEGIN TRY … END TRY BEGIN CATCH ROLLBACK; THROW; END CATCH. MySQL: DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN ROLLBACK; RESIGNAL; END. Oracle: EXCEPTION WHEN OTHERS THEN ROLLBACK; RAISE.
Распространённый антипаттерн — COMMIT внутри цикла. Если цикл рассчитан на 10 итераций и третья завершится ошибкой, первые две уже зафиксированы — ROLLBACK их не откатит. Правило: управление транзакцией выносится за пределы цикла, один COMMIT ставится после завершения всей логики.
Три объекта часто путают, но у каждого своя роль в архитектуре базы данных. Выбор зависит от задачи: выполнить операцию над данными, вычислить значение внутри запроса или автоматически отреагировать на событие в таблице.
| Характеристика |
Процедура |
Функция |
Триггер |
|---|---|---|---|
| Возврат значения | Необязательно (через OUT / INOUT) | Обязательно (одно значение) | Нет |
| Использование в SELECT | Нет | Да | Нет |
| Изменение данных (DML) | Да | Зависит от СУБД | Да |
| COMMIT / ROLLBACK | Да | Запрещено | В рамках транзакции события |
| Способ вызова | CALL / EXEC (явный) | В теле SQL-запроса | Автоматически при DML |
| Привязка к таблице | Нет | Нет | Да (INSERT / UPDATE / DELETE) |
Процедура — для операций над данными: вставка, обновление, управление транзакциями. Функция — для вычислений внутри запроса: рассчитать скидку, форматировать строку, вернуть агрегат. Триггер не принимает параметры и не вызывается явно — срабатывает автоматически при INSERT, UPDATE или DELETE на конкретной таблице в рамках той же транзакции.
Параметризованные хранимые процедуры по умолчанию защищены от SQL-инъекций: значение параметра передаётся как данные, а не как часть SQL-команды. Риск появляется при использовании динамического SQL — когда строка запроса собирается через конкатенацию входных данных.
Опасный паттерн:
EXECUTE ‘SELECT * FROM users WHERE name = »’ || p_name || »»;
Если p_name содержит ‘ OR ‘1’=’1, запрос вернёт все строки таблицы. Безопасная замена: EXECUTE … USING p_name в PostgreSQL или sp_executesql @sql, @params, @p_name = @p_name в SQL Server. В PostgreSQL для экранирования идентификаторов используют quote_ident(), для значений — quote_literal().
Права доступа выдаются на уровне процедуры: GRANT EXECUTE ON PROCEDURE get_report TO analyst_role. Пользователь с этим правом запускает процедуру, но не имеет прямого доступа к таблицам.
Два режима выполнения в PostgreSQL:

Хранимые процедуры эффективны там, где важны атомарность, повторяемость и единая точка бизнес-логики. Но они не универсальный инструмент — есть сценарии, где процедуры создают больше проблем, чем решают.
| Сценарий |
Рекомендация |
|---|---|
| Сложные аналитические отчёты с агрегацией | Использовать: снижение трафика, кэш плана |
| ETL-процессы (извлечение, преобразование, загрузка данных) | Использовать: атомарность, контроль транзакций |
| Финансовые операции (перевод средств, списание) | Использовать: атомарность и контроль безопасности |
| Единая логика для Web, API и мобильного клиента | Использовать: все клиенты работают через одну точку |
| Простой CRUD (создание, чтение, обновление, удаление) | Избегать: ORM или прямые запросы проще |
| Частые изменения бизнес-логики | Избегать: трудно версионировать и деплоить |
| Микросервисная архитектура | Избегать: процедуры создают жёсткую зависимость от СУБД |
| Необходимость смены СУБД | Избегать: переписывать придётся полностью |
Два архитектурных подхода: «толстая» процедура (Fat) — вся бизнес-логика в базе данных; «тонкая» (Thin) — только критичные атомарные операции. Разумный компромисс: атомарность и целостность данных — в процедурах, валидация ввода и UI-логика — в приложении.
Хранимая процедура — именованный набор SQL-команд и управляющей логики (условия, циклы, обработка ошибок), сохранённый на сервере базы данных. Создаётся один раз, вызывается командой CALL или EXEC с разными параметрами. Ключевые атрибуты: кэш плана выполнения, поддержка транзакций, инкапсуляция бизнес-логики.
Процедура не обязана возвращать значение, может изменять данные (INSERT/UPDATE/DELETE) и управляет транзакциями (COMMIT/ROLLBACK). Функция всегда возвращает одно значение, встраивается в SELECT/WHERE/JOIN, но COMMIT внутри запрещён. Используйте процедуру для операций над данными, функцию — для вычислений в теле запроса.
Процедура вызывается явно командой CALL/EXEC; триггер срабатывает автоматически при INSERT, UPDATE или DELETE на таблице. Процедура принимает параметры, триггер — нет. Оба объекта могут управлять транзакциями и изменять данные.
Используйте DELIMITER //, затем CREATE PROCEDURE имя(params) BEGIN … END//, верните разделитель: DELIMITER ;. Смена DELIMITER обязательна — без неё MySQL завершает команду на первой ; внутри тела процедуры.
Синтаксис: CREATE OR REPLACE PROCEDURE name(params) LANGUAGE plpgsql AS $$ BEGIN … END; $$. Двойной доллар ($$ … $$) — долларовое квотирование, избавляет от экранирования символов. Вызов: CALL name(params). Процедуры поддерживаются с PostgreSQL 11.
Синтаксис: CREATE PROCEDURE name @param TYPE AS BEGIN … END. Все параметры и переменные с префиксом @. Вызов: EXEC name или EXECUTE name @param = value. Для изменения — ALTER PROCEDURE без удаления объекта.
Три типа: IN — передаёт значение в процедуру (по умолчанию); OUT — процедура записывает результат, вызывающий код считывает; INOUT — передаёт и возвращает изменённое значение. В PostgreSQL поддерживаются DEFAULT-значения с именованными аргументами при вызове.
Параметризованные процедуры безопасны — значение параметра не интерпретируется как SQL. Риск возникает при динамическом SQL через конкатенацию строк. Защита: EXECUTE … USING или quote_ident() в PostgreSQL; sp_executesql в SQL Server.
Да, в процедурах COMMIT разрешён (в отличие от функций). Антипаттерн — COMMIT внутри цикла: если следующая итерация упадёт, предыдущие изменения уже зафиксированы и ROLLBACK их не откатит. Правило: одна транзакция на весь блок операций, COMMIT — только после полного завершения логики.
Подайте заявку —
забронируйте место в группе
45 000 мест на 2026 год. Бесплатное обучение по федеральному проекту «Активные меры содействия занятости»