Перейти к содержимому

Хранимые процедуры

Хранимые процедуры — программы, живущие внутри базы данных (SQL, условия, циклы, переменные, обработка ошибок). Преимущества: производительность (скомпилированный план), снижение сетевого трафика, инкапсуляция логики, безопасность (привилегии через процедуру), уменьшение дублирования кода. Недостатки: привязка к конкретной СУБД (PL/pgSQL, T-SQL, PL/SQL), сложность отладки и версионирования, разделение логики. Параметры: IN (входной), OUT (выходной), INOUT. Управляющие конструкции (IF, CASE, LOOP, WHILE), обработка ошибок (EXCEPTION, TRY/CATCH). Отличие от пользовательских функций (функции возвращают значение и могут быть в SELECT, процедуры изменяют данные и управляют транзакциями). Модели использования (толстая/тонкая процедура), безопасность (SQL-инъекции, динамический SQL), практические рекомендации (когда использовать, когда избегать).

Введение: Программа внутри базы данных

Представьте, что вы каждый день готовите сложный отчет для руководства. Вы пишете один и тот же запрос, потом другой, потом третий, объединяете результаты, перепроверяете данные. Это занимает час. А если бы вы могли сохранить всю последовательность действий под одним именем и просто вызывать ее — “сформировать отчет”?

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

Хранимая процедура (stored procedure) — это набор SQL-команд и логики управления (условия, циклы, переменные, обработка ошибок), который сохраняется на сервере базы данных и может выполняться по требованию.

В отличие от обычного SQL-запроса, который отправляется из приложения и выполняется один раз, хранимая процедура:

  • Хранится на сервере (не нужно передавать длинный текст запроса по сети).
  • Может содержать сложную логику (условия, циклы, временные таблицы).
  • Может принимать параметры и возвращать результаты.
  • Может вызывать другие хранимые процедуры.

Зачем нужны хранимые процедуры

Плюсы использования

Производительность:

Хранимая процедура компилируется один раз (при создании или при первом выполнении), и план выполнения сохраняется. При последующих вызовах база данных не тратит время на разбор и оптимизацию запроса. Для сложных операций, выполняемых многократно, выигрыш может быть значительным.

Снижение сетевого трафика:

Вместо того чтобы отправлять на сервер десять длинных SQL-запросов, приложение отправляет одно короткое сообщение: “выполни процедуру X с параметрами Y”. Это особенно важно, когда приложение и база данных находятся на разных серверах.

Инкапсуляция логики:

Хранимая процедура скрывает детали реализации. Приложение знает только: “вызови процедуру перевода денег с параметрами (откуда, куда, сумма)”. Как именно происходит перевод, проверка баланса, запись в журнал — это детали, скрытые внутри процедуры. Если логика изменится, процедуру можно изменить в одном месте, не трогая приложение.

Безопасность:

Можно дать пользователю право выполнять хранимую процедуру, но не давать прямого доступа к таблицам. Пользователь сможет, например, перевести деньги через процедуру, но не сможет напрямую изменить баланс или прочитать чужие данные. Это называется “привилегии через процедуру”.

Уменьшение дублирования кода:

Если одна и та же бизнес-операция нужна в разных местах (веб-интерфейс, мобильное приложение, API для партнеров), ее логика пишется один раз в хранимой процедуре. Все приложения вызывают одну и ту же процедуру, гарантируя одинаковое поведение.

Минусы и ограничения

МинусОписание
Привязка к конкретной СУБДХранимые процедуры пишутся на языке, специфичном для вашей базы данных (PL/pgSQL в PostgreSQL, T-SQL в SQL Server, PL/SQL в Oracle). Перенос на другую СУБД потребует переписывания
Сложность отладкиОтлаживать хранимые процедуры сложнее, чем код на Python, Java или C#. Инструменты отладки есть, но они менее удобны
Сложность версионированияХранимые процедуры хранятся в базе данных, а не в системе контроля версий (если не настроено специальное решение). Изменение процедуры напрямую в продакшене — рискованно
Разделение логикиЛогика оказывается разделенной между приложением и базой данных. Новому разработчику сложнее понять, где что находится
МасштабированиеБаза данных — это единая точка. Если логика сильно нагружает процессор, вы не можете “добавить еще один сервер базы данных” так же легко, как добавить сервер приложения

Синтаксис и базовая структура

Синтаксис хранимых процедур сильно различается в разных СУБД. Ниже приведены примеры для самых популярных.

PostgreSQL (PL/pgSQL)

-- Создание процедуры
CREATE OR REPLACE PROCEDURE transfer_money(
    p_from_account INT,
    p_to_account INT,
    p_amount DECIMAL
)
LANGUAGE plpgsql
AS $$
DECLARE
    v_balance DECIMAL;
BEGIN
    -- Проверка остатка на счете отправителя
    SELECT balance INTO v_balance 
    FROM accounts 
    WHERE id = p_from_account 
    FOR UPDATE;
    
    IF v_balance < p_amount THEN
        RAISE EXCEPTION 'Недостаточно средств';
    END IF;
    
    -- Списание и зачисление
    UPDATE accounts SET balance = balance - p_amount WHERE id = p_from_account;
    UPDATE accounts SET balance = balance + p_amount WHERE id = p_to_account;
    
    -- Запись в журнал
    INSERT INTO transactions (from_account, to_account, amount, created_at)
    VALUES (p_from_account, p_to_account, p_amount, NOW());
    
    COMMIT;
END;
$$;

-- Вызов процедуры
CALL transfer_money(1, 2, 100.00);

SQL Server (T-SQL)

-- Создание процедуры
CREATE PROCEDURE transfer_money
    @from_account INT,
    @to_account INT,
    @amount DECIMAL(10,2)
AS
BEGIN
    SET NOCOUNT ON;
    DECLARE @balance DECIMAL(10,2);
    
    BEGIN TRANSACTION;
    
    -- Проверка остатка
    SELECT @balance = balance 
    FROM accounts WITH (UPDLOCK) 
    WHERE id = @from_account;
    
    IF @balance < @amount
    BEGIN
        ROLLBACK;
        RAISERROR('Недостаточно средств', 16, 1);
        RETURN;
    END;
    
    -- Списание и зачисление
    UPDATE accounts SET balance = balance - @amount WHERE id = @from_account;
    UPDATE accounts SET balance = balance + @amount WHERE id = @to_account;
    
    -- Запись в журнал
    INSERT INTO transactions (from_account, to_account, amount, created_at)
    VALUES (@from_account, @to_account, @amount, GETDATE());
    
    COMMIT TRANSACTION;
END;

-- Вызов процедуры
EXEC transfer_money @from_account = 1, @to_account = 2, @amount = 100.00;

MySQL

-- Создание процедуры
DELIMITER //

CREATE PROCEDURE transfer_money(
    IN p_from_account INT,
    IN p_to_account INT,
    IN p_amount DECIMAL(10,2)
)
BEGIN
    DECLARE v_balance DECIMAL(10,2);
    DECLARE EXIT HANDLER FOR SQLEXCEPTION
    BEGIN
        ROLLBACK;
        RESIGNAL;
    END;
    
    START TRANSACTION;
    
    -- Проверка остатка
    SELECT balance INTO v_balance 
    FROM accounts 
    WHERE id = p_from_account 
    FOR UPDATE;
    
    IF v_balance < p_amount THEN
        ROLLBACK;
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Недостаточно средств';
    END IF;
    
    -- Списание и зачисление
    UPDATE accounts SET balance = balance - p_amount WHERE id = p_from_account;
    UPDATE accounts SET balance = balance + p_amount WHERE id = p_to_account;
    
    -- Запись в журнал
    INSERT INTO transactions (from_account, to_account, amount, created_at)
    VALUES (p_from_account, p_to_account, p_amount, NOW());
    
    COMMIT;
END//

DELIMITER ;

-- Вызов процедуры
CALL transfer_money(1, 2, 100.00);

Oracle (PL/SQL)

-- Oracle различает процедуры и функции
CREATE OR REPLACE PROCEDURE transfer_money(
    p_from_account IN accounts.id%TYPE,
    p_to_account IN accounts.id%TYPE,
    p_amount IN accounts.balance%TYPE
) AS
    v_balance accounts.balance%TYPE;
BEGIN
    -- Проверка остатка (с блокировкой строки)
    SELECT balance INTO v_balance 
    FROM accounts 
    WHERE id = p_from_account 
    FOR UPDATE;
    
    IF v_balance < p_amount THEN
        RAISE_APPLICATION_ERROR(-20001, 'Недостаточно средств');
    END IF;
    
    -- Списание и зачисление
    UPDATE accounts SET balance = balance - p_amount WHERE id = p_from_account;
    UPDATE accounts SET balance = balance + p_amount WHERE id = p_to_account;
    
    -- Запись в журнал
    INSERT INTO transactions (from_account, to_account, amount, created_at)
    VALUES (p_from_account, p_to_account, p_amount, SYSDATE);
    
    COMMIT;
EXCEPTION
    WHEN OTHERS THEN
        ROLLBACK;
        RAISE;
END transfer_money;
/

-- Вызов процедуры (в анонимном блоке)
BEGIN
    transfer_money(1, 2, 100.00);
END;
/

Параметры хранимых процедур

Хранимые процедуры могут принимать параметры и возвращать значения. В разных СУБД типы параметров различаются.

Типы параметров

ТипНаправлениеОписание
INВходнойЗначение передается в процедуру. Процедура не может его изменить
OUTВыходнойПроцедура возвращает значение через этот параметр
INOUTВходной и выходнойЗначение передается в процедуру, процедура может его изменить и вернуть новое

Пример с OUT-параметрами (PostgreSQL):

CREATE OR REPLACE PROCEDURE get_user_stats(
    p_user_id INT,
    OUT p_order_count INT,
    OUT p_total_amount DECIMAL
)
LANGUAGE plpgsql
AS $$
BEGIN
    SELECT COUNT(*), SUM(amount)
    INTO p_order_count, p_total_amount
    FROM orders
    WHERE user_id = p_user_id;
END;
$$;

-- Вызов с OUT-параметрами
CALL get_user_stats(1, NULL, NULL);  -- NULL на месте OUT-параметров

Пример с INOUT-параметром (SQL Server):

CREATE PROCEDURE add_tax
    @amount DECIMAL(10,2) INOUT,
    @tax_rate DECIMAL(5,2)
AS
BEGIN
    SET @amount = @amount * (1 + @tax_rate / 100);
END;

-- Использование
DECLARE @price DECIMAL(10,2) = 100.00;
EXEC add_tax @amount = @price INOUT, @tax_rate = 20;
SELECT @price;  -- 120.00

Параметры со значениями по умолчанию

-- PostgreSQL
CREATE OR REPLACE PROCEDURE search_products(
    p_category VARCHAR DEFAULT NULL,
    p_min_price DECIMAL DEFAULT 0,
    p_max_price DECIMAL DEFAULT 999999
)
LANGUAGE plpgsql
AS $$
BEGIN
    -- тело процедуры
END;
$$;

-- Вызов с использованием значений по умолчанию
CALL search_products();  -- все параметры по умолчанию
CALL search_products(p_category => 'books');  -- только категория

Управляющие конструкции

Хранимые процедуры поддерживают полноценные языки программирования с условиями, циклами и переменными.

Переменные

-- PostgreSQL
DECLARE
    v_counter INT := 0;
    v_name VARCHAR(100);
    v_balance DECIMAL DEFAULT 0.00;
    v_is_active BOOLEAN;

Условные операторы

-- PostgreSQL
IF v_balance < 0 THEN
    RAISE EXCEPTION 'Отрицательный баланс';
ELSIF v_balance = 0 THEN
    v_status := 'empty';
ELSE
    v_status := 'positive';
END IF;

-- SQL Server (CASE выражение и IF)
IF @balance < 0
BEGIN
    RAISERROR('Отрицательный баланс', 16, 1);
    RETURN;
END
ELSE IF @balance = 0
    SET @status = 'empty';
ELSE
    SET @status = 'positive';

-- MySQL
IF v_balance < 0 THEN
    SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Отрицательный баланс';
ELSEIF v_balance = 0 THEN
    SET v_status = 'empty';
ELSE
    SET v_status = 'positive';
END IF;

-- CASE в любых СУБД (для вычисления значения)
v_status := CASE 
    WHEN v_balance < 0 THEN 'negative'
    WHEN v_balance = 0 THEN 'empty'
    ELSE 'positive'
END;

Циклы

-- PostgreSQL (LOOP с выходом)
LOOP
    v_counter := v_counter + 1;
    EXIT WHEN v_counter > 10;
    -- тело цикла
END LOOP;

-- PostgreSQL (WHILE)
WHILE v_counter <= 10 LOOP
    -- тело цикла
    v_counter := v_counter + 1;
END LOOP;

-- PostgreSQL (FOR по диапазону)
FOR i IN 1..10 LOOP
    -- тело цикла
END LOOP;

-- PostgreSQL (FOR по результату запроса)
FOR rec IN SELECT id, name FROM users LOOP
    RAISE NOTICE 'User: %, %', rec.id, rec.name;
END LOOP;

-- SQL Server
WHILE @counter <= 10
BEGIN
    -- тело цикла
    SET @counter = @counter + 1;
END;

-- MySQL
WHILE v_counter <= 10 DO
    -- тело цикла
    SET v_counter = v_counter + 1;
END WHILE;

Обработка ошибок

Хранимые процедуры могут перехватывать и обрабатывать ошибки, выполнять откат транзакций и логировать проблемы.

Обработка ошибок в PostgreSQL

CREATE OR REPLACE PROCEDURE safe_transfer(
    p_from INT, p_to INT, p_amount DECIMAL
)
LANGUAGE plpgsql
AS $$
BEGIN
    BEGIN  -- вложенный блок для обработки ошибок
        -- основная логика
        UPDATE accounts SET balance = balance - p_amount WHERE id = p_from;
        UPDATE accounts SET balance = balance + p_amount WHERE id = p_to;
        
    EXCEPTION
        WHEN check_violation THEN
            RAISE NOTICE 'Ошибка проверки ограничения';
            ROLLBACK;
        WHEN foreign_key_violation THEN
            RAISE NOTICE 'Неверный идентификатор счета';
            ROLLBACK;
        WHEN OTHERS THEN
            RAISE NOTICE 'Неизвестная ошибка: %', SQLERRM;
            ROLLBACK;
    END;
END;
$$;

Обработка ошибок в SQL Server

CREATE PROCEDURE safe_transfer
    @from INT, @to INT, @amount DECIMAL
AS
BEGIN
    BEGIN TRY
        BEGIN TRANSACTION;
        
        UPDATE accounts SET balance = balance - @amount WHERE id = @from;
        UPDATE accounts SET balance = balance + @amount WHERE id = @to;
        
        COMMIT TRANSACTION;
    END TRY
    BEGIN CATCH
        ROLLBACK TRANSACTION;
        
        -- Логирование ошибки
        INSERT INTO error_log (error_message, error_number, error_line)
        VALUES (
            ERROR_MESSAGE(),
            ERROR_NUMBER(),
            ERROR_LINE()
        );
        
        -- Перебросить ошибку выше
        THROW;
    END CATCH
END;

Обработка ошибок в MySQL

CREATE PROCEDURE safe_transfer(
    p_from INT, p_to INT, p_amount DECIMAL
)
BEGIN
    DECLARE EXIT HANDLER FOR SQLEXCEPTION
    BEGIN
        ROLLBACK;
        -- Можно логировать ошибку
        RESIGNAL;
    END;
    
    START TRANSACTION;
    UPDATE accounts SET balance = balance - p_amount WHERE id = p_from;
    UPDATE accounts SET balance = balance + p_amount WHERE id = p_to;
    COMMIT;
END;

Хранимые процедуры vs Пользовательские функции

Это важное различие, которое нужно понимать. Пользовательские функции будут рассмотрены в отдельном документе, но здесь приведем краткое сравнение.

ХарактеристикаХранимая процедураПользовательская функция
Возвращает значениеНеобязательно (OUT-параметры)Обязательно (один результат)
Может быть в SELECTНетДа
Может изменять данныеДаНет (кроме табличных функций)
Может управлять транзакциямиДаНет (COMMIT/ROLLBACK запрещены)
Может вызывать процедурыДаОграниченно
Использование в WHEREНетДа

Когда использовать процедуру, а когда функцию:

СценарийВыбор
Нужно изменить данные (INSERT, UPDATE, DELETE)Процедура
Нужно управлять транзакциями (COMMIT/ROLLBACK)Процедура
Нужно вернуть одно значение для использования в SELECTФункция
Нужно использовать в WHERE, JOIN, HAVINGФункция
Нужно вернуть результирующий набор для отчетаПроцедура (или табличная функция)
Нужно выполнить сложную бизнес-логику с несколькими шагамиПроцедура

Хранимые процедуры в архитектуре приложений

Модели использования

Толстая процедура (Fat Procedure):

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

Плюсы: максимальная производительность (нет сетевых вызовов между шагами), централизованная логика.

Минусы: привязка к СУБД, сложность отладки и версионирования.

Тонкая процедура (Thin Procedure):

В хранимых процедурах только простые операции, часто используемые в разных местах. Основная бизнес-логика в приложении.

Плюсы: гибкость, простота разработки, независимость от СУБД.

Минусы: больше сетевых вызовов, больше кода в приложении.

Компромиссный подход:

  • Критичные по производительности операции — в процедурах.
  • Сложная бизнес-логика с частыми изменениями — в приложении.
  • Операции, требующие строгой атомарности и изоляции — в процедурах (с транзакциями).

Практические рекомендации

Когда стоит использовать хранимые процедуры:

СценарийПочему
Сложные отчеты с множеством объединений и агрегацийМеньше данных передается по сети, процедура может использовать временные таблицы
Пакетная обработка данных (ETL, массовые обновления)Можно запустить на сервере, не передавая миллионы строк в приложение
Операции, требующие строгой атомарностиВся логика в одном месте, проще управлять транзакцией
Однотипные операции с разных клиентовЕдиная логика для веб-приложения, API, мобильного приложения
Действия, которые должны выполняться с повышенными привилегиямиПроцедура может делать то, что запрещено пользователю напрямую

Когда стоит избегать хранимых процедур:

СценарийПочему
Простая бизнес-логика (CRUD)ORM справляется отлично, процедуры усложняют
Логика, которая часто меняетсяИзменять процедуру сложнее, чем код приложения
КроссплатформенностьПроцедуры привязывают к конкретной СУБД
Микросервисная архитектураЛогика должна быть в сервисе, а не в общей базе
Слабая команда по базам данныхРазработчикам приложений проще писать на знакомом языке

Безопасность хранимых процедур

Привилегии через процедуру

Один из мощнейших механизмов безопасности в базах данных. Вместо того чтобы давать пользователю прямой доступ к таблицам, вы даете ему право выполнять процедуру. Сама процедура выполняется с правами ее владельца (или с правами вызывающего — зависит от настройки).

-- PostgreSQL: процедура выполняется с правами владельца
ALTER PROCEDURE transfer_money OWNER TO trusted_role;
GRANT EXECUTE ON PROCEDURE transfer_money TO public;

-- Пользователи могут вызвать процедуру, но не имеют доступа к таблицам accounts

SQL-инъекции и хранимые процедуры

Хранимые процедуры, правильно написанные с использованием параметров, защищены от SQL-инъекций. Динамический SQL внутри процедуры (когда вы строите запрос как строку) снова становится уязвимым.

-- Безопасно: параметризованный запрос
CREATE PROCEDURE get_user(p_user_id INT)
LANGUAGE plpgsql
AS $$
BEGIN
    SELECT * FROM users WHERE id = p_user_id;
END;
$$;

-- Опасно: динамический SQL без экранирования
CREATE PROCEDURE search_users(p_name VARCHAR)
LANGUAGE plpgsql
AS $$
BEGIN
    EXECUTE 'SELECT * FROM users WHERE name = ''' || p_name || '''';
    -- Если p_name = "'; DROP TABLE users; --" — катастрофа
END;
$$;

Если динамический SQL неизбежен, используйте безопасные функции экранирования (quote_ident, quote_literal в PostgreSQL, QUOTENAME в SQL Server).

Отладка и мониторинг хранимых процедур

Вывод отладочной информации

-- PostgreSQL
RAISE NOTICE 'Значение переменной: %', v_variable;
RAISE DEBUG 'Отладочная информация';

-- SQL Server
PRINT 'Значение переменной: ' + CAST(@variable AS VARCHAR);

-- MySQL (использование переменных сессии)
SET @debug_message = CONCAT('Значение: ', v_variable);

Поиск медленных процедур

В большинстве СУБД есть системные представления, показывающие статистику выполнения процедур.

-- PostgreSQL (с расширением pg_stat_statements)
SELECT query, calls, total_time, mean_time
FROM pg_stat_statements
WHERE query LIKE '%transfer_money%'
ORDER BY mean_time DESC;

-- SQL Server (DMV)
SELECT 
    OBJECT_NAME(object_id) AS procedure_name,
    execution_count,
    total_worker_time / execution_count AS avg_cpu_time
FROM sys.dm_exec_procedure_stats
ORDER BY avg_cpu_time DESC;

Хранимые процедуры в разных СУБД: Особенности

PostgreSQL

  • Поддерживает процедуры с CREATE PROCEDURE (с PostgreSQL 11). Ранее использовались функции для всего.
  • Процедуры могут управлять транзакциями (COMMIT/ROLLBACK внутри).
  • Мощный язык PL/pgSQL с поддержкой массивов, JSON, курсоров.
  • Поддержка перегрузки процедур (несколько процедур с одним именем, но разными параметрами).

Особенность: В PostgreSQL процедуры не возвращают результирующий набор (для этого есть функции, возвращающие SETOF или TABLE).

SQL Server

  • Процедуры могут возвращать несколько результирующих наборов.
  • Поддержка курсоров, временных таблиц, табличных переменных.
  • Мощная обработка ошибок через TRY/CATCH.
  • Поддержка динамического SQL через sp_executesql.

Особенность: SQL Server различает процедуры и функции более строго, чем PostgreSQL.

Oracle

  • PL/SQL — очень мощный язык, близкий к Ada/Pascal.
  • Пакеты (packages) — способ группировать процедуры, функции, типы.
  • Автономные транзакции (PRAGMA AUTONOMOUS_TRANSACTION) — подтранзакции, которые фиксируются независимо от родительской транзакции.

Особенность: В Oracle все хранимые объекты (процедуры, функции, пакеты) компилируются в байт-код для повышения производительности.

MySQL

  • Процедуры поддерживаются, но язык менее развит, чем в PostgreSQL или Oracle.
  • Ограниченная поддержка массивов (нет встроенного типа array).
  • Обработка ошибок через декларативные обработчики.

Особенность: MySQL требует смены разделителя (DELIMITER) при создании процедур, чтобы тело процедуры не интерпретировалось как отдельные команды.

Распространенные ошибки при работе с хранимыми процедурами

Ошибка 1: Дублирование логики между процедурами

Одна и та же проверка баланса реализована в десяти разных процедурах. Когда правила меняются, нужно менять в десяти местах.

Как исправить: Выделять общую логику в отдельные процедуры или функции. Вызывать их из других процедур.

Ошибка 2: Слишком длинные процедуры

Процедура на 2000 строк кода. Невозможно понять, что она делает, невозможно отладить, невозможно протестировать.

Как исправить: Разбивать на маленькие процедуры (20-50 строк каждая). Каждая процедура делает одно действие.

Ошибка 3: Смешивание уровней абстракции

Процедура, которая и обновляет баланс, и форматирует отчет, и отправляет email через внешний вызов.

Как исправить: Разделять бизнес-логику, операции с данными и внешние вызовы. Внешние вызовы не должны быть внутри транзакции.

Ошибка 4: Игнорирование обработки ошибок

Процедура делает UPDATE, потом UPDATE, потом COMMIT. Если второе обновление падает, первое остается незафиксированным (или частично зафиксированным, в зависимости от СУБД).

Как исправить: Всегда использовать обработку ошибок. В случае ошибки — ROLLBACK.

Ошибка 5: Неправильное управление транзакциями

-- Плохо: COMMIT внутри цикла
CREATE PROCEDURE process_orders()
AS $$
BEGIN
    FOR rec IN SELECT * FROM orders WHERE status = 'pending' LOOP
        UPDATE orders SET status = 'processed' WHERE id = rec.id;
        COMMIT;  -- Не делайте так!
    END LOOP;
END;
$$;

После COMMIT транзакция завершается. Если следующий UPDATE упадет, предыдущие изменения уже зафиксированы и не будут откачены. Частичная обработка — проблемы с целостностью.

Как исправить: Либо одна транзакция на весь цикл, либо разбивать на отдельные вызовы процедуры (каждый вызов — своя транзакция).

Ошибка 6: Не использовать схему именования параметров

-- Плохо: имя параметра совпадает с именем столбца
CREATE PROCEDURE update_user(id INT, name VARCHAR)
AS $$
BEGIN
    UPDATE users SET name = name WHERE id = id;  -- name = name? id = id?
END;
$$;

Как исправить: Использовать префиксы: p_id, p_name, in_id, v_name или явно указывать таблицу: users.name, users.id.

Резюме для системного аналитика

  1. Хранимая процедура — это программа внутри базы данных. Она содержит SQL-команды и логику управления. Вызывается из приложения, других процедур или триггеров.

  2. Главные преимущества: производительность (меньше сетевых вызовов, скомпилированный план), безопасность (привилегии через процедуру), инкапсуляция (скрытие деталей), переиспользование кода.

  3. Главные недостатки: привязка к конкретной СУБД, сложность отладки, проблемы с версионированием, разделение логики между приложением и базой.

  4. Параметры бывают IN (входные), OUT (выходные), INOUT (входные и выходные). OUT-параметры позволяют возвращать значения.

  5. Процедуры поддерживают полноценные языки программирования: переменные, условия (IF/CASE), циклы (WHILE/FOR), обработку ошибок.

  6. Процедуры могут управлять транзакциями (COMMIT/ROLLBACK). Это ключевое отличие от пользовательских функций.

  7. Синтаксис сильно различается между СУБД. PL/pgSQL (PostgreSQL), T-SQL (SQL Server), PL/SQL (Oracle) — это разные языки. Перенос между СУБД требует переписывания.

Проверка знаний

Вопрос 1 из 4
Что такое хранимая процедура?
Когда хранимые процедуры особенно уместны?
Какой риск связан с чрезмерным использованием процедур?
Что важно при работе с процедурами?

Вопросы, где были ошибки