Хранимые процедуры (компонент Database Engine)
Хранимая процедура в SQL Server представляет собой группу из одного или нескольких операторов Transact-SQL или ссылку на метод среды CLR Microsoft .NET Framework. Процедуры аналогичны конструкциям в других языках программирования, поскольку обеспечивают следующее:
- обрабатывают входные параметры и возвращают вызывающей программе значения в виде выходных параметров;
- содержат программные инструкции, которые выполняют операции в базе данных, включая вызов других процедур;
- возвращают значение состояния вызывающей программе, таким образом передавая сведения об успешном или неуспешном завершении (и причины последнего).
Преимущества хранимых процедур
В следующем списке описываются преимущества использования процедур.
Снижение сетевого трафика между клиентами и сервером
Команды в процедуре выполняются как один пакет кода. Это позволяет существенно сократить сетевой трафик между сервером и клиентом, поскольку по сети отправляется только вызов на выполнение процедуры. Без инкапсуляции кода, предоставляемой процедурой, по сети бы пришлось пересылать все отдельные строки кода.
Большая безопасность
Многие пользователи и клиентские программы могут выполнять операции с базовыми объектами базы данных посредством процедур, даже если у них нет прямых разрешений на доступ к базовым объектам. Процедура проверяет, какие из процессов и действий могут выполняться, и защищает базовые объекты базы данных. Это устраняет необходимость предоставлять разрешения на уровне индивидуальных объектов и упрощает формирование уровней безопасности.
Предложение EXECUTE AS может быть указано в инструкции CREATE PROCEDURE, чтобы разрешить олицетворение других пользователей или разрешить пользователям или приложениям выполнять определенные действия баз данных без необходимости иметь прямые разрешения на базовые объекты и команды. Например, для некоторых действий, таких как TRUNCATE TABLE, предоставить разрешения нельзя. Чтобы выполнить инструкцию TRUNCATE TABLE, у пользователя должны быть разрешения ALTER на нужную таблицу. Предоставление разрешений ALTER не всегда подходит, так как фактические разрешения пользователя выходят за пределы возможности усечения таблицы. Заключив инструкцию TRUNCATE TABLE в модуль и указав, что этот модуль должен выполняться от имени пользователя, у которого есть разрешения на изменение таблицы, можно предоставить разрешение на усечение таблицы пользователю с разрешением EXECUTE для этого модуля.
При вызове процедуры через сеть виден только вызов на выполнение процедуры. Таким образом, злоумышленники не могут просматривать имена объектов таблиц и баз данных, внедрять собственные инструкции Transact-SQL или выполнять поиск критически важных данных.
Использование параметров в процедурах помогает предотвратить атаки типа «инъекция SQL». Так как входные данные параметра обрабатываются как литеральное значение, а не как исполняемый код, злоумышленнику сложнее вставить команду в инструкции Transact-SQL внутри процедуры и нарушить безопасность.
Процедуры могут быть зашифрованы, что позволяет замаскировать исходный код. Дополнительные сведения см. в статье SQL Server Encryption.
Повторное использование кода
Если какой-то код многократно используется в операции базы данных, то отличным решением будет произвести его инкапсуляцию в процедуры. Это устранит необходимость излишнего копирования того же кода, снизит уровень несогласованности кода и позволит осуществлять доступ к коду любым пользователям или приложениям, имеющим необходимые разрешения.
Более легкое обслуживание
Если клиентские приложения вызывают процедуры, а операции баз данных остаются на уровне данных, то для внесения изменений в основную базу данных будет достаточно обновить только процедуры. Уровень приложения остается незатронутым изменениями в схемах баз данных, связях или процессах.
повышение производительности.
По умолчанию компиляция процедуры и создание плана выполнения, используемого для последующих выполнений, производится при ее первом запуске Поскольку обработчику запросов не нужно создавать новый план, обычно обработка процедуры занимает меньше времени.
Если в таблицах или данных, на которые ссылается процедура, произошли значительные изменения, то наличие предварительно скомпилированного плана может вызвать замедление работы процедуры. В этом случае перекомпиляция процедуры и принудительное создание нового плана выполнения может улучшить производительность.
Типы хранимых процедур
Пользовательские процедуры
Пользовательские процедуры могут быть созданы в пользовательской базе данных или любых системных базах данных, за исключением базы данных Resource . Процедура может быть разработана в Transact-SQL или в качестве ссылки на метод Microsoft платформа .NET Framework CLR.
Временные процедуры
Временные процедуры — это один из видов пользовательских процедур. Временные процедуры схожи с постоянными процедурами, за исключением того, что они хранятся в базе данных tempdb. Существует два вида временных процедур: локальные и глобальные. Они отличаются друг от друга именами, видимостью и доступностью. Имена локальных временных процедур начинаются с одного знака диеза (#); они видны только текущему соединению пользователя и удаляются, когда закрывается соединение. Имена глобальных временных процедур начинаются с двух знаков диеза (##); они видны любому пользователю и удаляются после окончания последнего сеанса, использующего процедуру.
Системные функции
Системные процедуры включены в SQL Server. Физически они хранятся во внутренней скрытой базе данных Resource . Логически они отображаются в схеме sys каждой системной и пользовательской базы данных. В дополнение к этому, база данных msdb также содержит системные хранимые процедуры в схеме dbo . Эти процедуры используются для планирования предупреждений и заданий. Поскольку названия системных процедур начинаются с префикса sp_ , этот префикс не рекомендуется использовать при создании пользовательских процедур. Полный список системных процедур см. в разделе Системные хранимые процедуры (Transact-SQL).
SQL Server поддерживает системные процедуры, предоставляющие интерфейс от SQL Server до внешних программ для различных действий по обслуживанию. Эти расширенные процедуры имеют префикс xp_. Полный список расширенных процедур см. в разделе Общие расширенные хранимые процедуры (Transact-SQL).
Расширенные пользовательские процедуры
Расширенные процедуры позволяют создавать внешние подпрограммы на языке программирования, таком как C. Эти процедуры представляют собой библиотеки DLL, которые экземпляр SQL Server может динамически загружать и запускать.
Расширенные хранимые процедуры будут удалены в следующей версии SQL Server. Не используйте его при работе над новыми приложениями и как можно быстрее измените приложения, в которых он в настоящее время используется. Вместо них рекомендуется создавать процедуры CLR. Этот метод более надежен и безопасен, чем использование расширенных хранимых процедур.
Связанные задачи
| Описание задачи | Раздел |
|---|---|
| Описывает создание хранимой процедуры. | Создание хранимой процедуры |
| Описывает изменение хранимой процедуры. | Изменение хранимой процедуры |
| Описывает удаление хранимой процедуры. | Удаление хранимой процедуры |
| Описывает выполнение хранимой процедуры. | Выполнение хранимой процедуры |
| Описывает предоставление разрешений на хранимую процедуру. | Предоставление разрешений на хранимую процедуру |
| Описывает возврат данных из хранимой процедуры в приложение. | Возврат данных из хранимой процедуры |
| Описывает перекомпиляцию хранимой процедуры. | Перекомпиляция хранимой процедуры |
| Описывает переименование хранимой процедуры. | Изменение имени хранимой процедуры |
| Описывает просмотр определения хранимой процедуры. | Просмотр определения хранимой процедуры |
| Описывает просмотр зависимостей хранимой процедуры. | Просмотр зависимостей хранимой процедуры |
| Описывается использование параметров в хранимой процедуре. | Параметры |
SQL-Ex blog
Хранимые процедуры SQL: входные и выходные параметры, типы, обработка ошибок и кое-что еще
Добавил Sergey Moiseenko on Суббота, 25 февраля. 2023
Если вы слышали о хранимых процедурах в SQL Server, но точно не знаете, что это такое и как это использовать наилучшим образом, то эта статья даст вам необходимую информацию и позволит начать работать с хранимыми процедурами.
Здесь вы узнаете, что из себя представляет хранимая процедура, как ее создать и применить. Мы поговорим о параметрах хранимой процедуры, входных и выходных, и вкратце обсудим обработку ошибок и безопасность для хранимых процедур.
Что необходимо для выполнения примеров
Примеры в этой статье выполняются к учебной базе данных AdventureWorks. Получив практику на учебной базе данных, вы сможете легко применить эти примеры к вашей базе данных. Как обычно, остерегайтесь экспериментировать с вашей производственной системой.
Что такое хранимая процедура SQL?
В простейшем виде хранимая процедура — это не более чем набор операторов Transact-SQL (T-SQL), который хранится в базе данных и может выполняться при вызове хранимой процедуры по имени. Имя присваивается хранимой процедуре при ее создании. Для краткости хранимую процедуру часто называют хп (SP).
Особенности хранимых процедур
- Хранимые процедуры могут принимать входные параметры.
- Хранимые процедуры могут возвращать выходные параметры.
- Хранимые процедуры содержат программные операторы.
- Хранимые процедуры могут возвращать значение состояния, показывающее успешность или сбой (и почему он произошел).
- Нет ограничения на размер хранимой процедуры
- Имеется четыре типа хранимых процедур:
- Определяемые пользователем
- Временные
- Системные
- Расширенные определяемые пользователем
Определения четырех типов хранимых процедур
Определяемая пользователем хранимая процедура
Наиболее часто используются пользовательские хранимые процедуры. Такая процедура может создаваться в пользовательской базе данных или в любой системной базе данных за исключением базы данных Resource. Мы подробней поговорим о пользовательских хранимых процедурах позже в этой статье и рассмотрим ряд примеров.
Временная хранимая процедура
Временная хранимая процедура — это вид пользовательской хранимой процедуры, которая очень похожа на временные таблицы. Временные хранимые процедуры хранятся в базе данных «tempdb», видной на вкладке «System Databases». Эти временные хранимые процедуры могут использоваться как локальные или глобальные.
Временные локальные хранимые процедуры видны только на текущем подключении пользователя и всегда будут иметь префикс # (знак фунта или хэштега).
Временные глобальные хранимые процедуры видны на любом пользовательском подключении и имеют префикс ## (двойной знак фунта).
Поскольку они являются «временными», эти хранимые процедуры пропадают, когда закрывается подключение SQL.
Системные хранимые процедуры
Вы обнаружите, что довольно часто используете системные хранимые процедуры. Полезно познакомиться с ними, чтобы не изобретать колесо. Эти хранимые процедуры включены по умолчанию в установку SQL Server и всегда будут начинаться с префикса «sys.sp_». Вам могут пригодиться самые распространенные системные хранимые процедуры, включая sys.sp_addUser, sys.sp_addLogin, sys.sp_addRole, sys.sp_change_users_login и т.д.
- Databases (базы данных)
- System Databases (системные базы данных)
- Tempdb
- Programmability (программирование)
- Stored Procedures (хранимые процедуры)
- System Stored Procedures (системные хранимые процедуры)

Расширенные хранимые процедуры пользователя
Расширенные хранимые процедуры пользователя используются для создания внешних процедур на языках типа C, C#, VB и т.д. Они реализуются как DLL, которые SQL Server может загрузить и выполнять динамически.
Однако, согласно Microsoft, расширенные хранимые процедуры пользователя будут удалены из будущих версий SQL Server. Поэтому не рекомендуется использовать их в текущих или будущих разработках. Вам следует запланировать удаление или модификацию приложений, использующих эти хранимые процедуры.
Зачем использовать хранимые процедуры
Хранимые процедуры имеют много преимуществ. Он уменьшают сетевой трафик, поскольку только имя передается по сети от приложения на сервер. Правда, мы можем передавать еще несколько параметров, но передача имени хранимой процедуры и нескольких параметров не так сильно загружает сеть как передача всего кода всякий раз, когда нужно этот код выполнить.
Хранимые процедуры усиливают безопасность. Код в хранимой процедуре определяет, какие действия производятся над объектами базы данных. Это означает, что при всяком вызове хранимой процедуры один и тот же блок кода выполняется одним и тем же способом. Вы не должны беспокоиться об ошибках в коде, повторяя набор одного и того же кода всякий раз, когда вам нужно выполнить его.
Мы можем предоставить пользователям разрешение на использование (вызов) хранимой процедуры или же запретить пользователям ее вызывать. Замечание: пользователь не должен иметь разрешение или доступ к таблице, чтобы вызвать хранимую процедуру, которая будет менять данные в этой таблице. Это позволит пользователям добавлять данные в таблицу для создания записей данных, не имея к ним доступа на просмотр, удаление или изменение любых данных в таблице, помимо тех, на которые вы дали им разрешения.
Создав хранимую процедуру, вы выполняете ее, создается план выполнения запроса, который сохраняется и повторно используется. Если вы создаете базисный шаблон вашего кода SQL или набираете код вручную всякий раз, когда вам его нужно выполнить, SQL должен создавать новый план выполнения запроса. Это замедляет время выполнения вашего кода и потребляет больше ресурсов. Поскольку мы сохраняем и повторно используем существующий план выполнения, хранимая процедура может выполняться значительно быстрее и использовать меньше ресурсов, т.к. нет необходимости создавать план выполнения. Просто загружается план, созданный ранее.
Создание простой хранимой процедуры
Теперь, когда мы знаем немного больше о преимуществах хранимых процедур, давайте создадим базовый тестовый пример для работы. В коде примера, приведенного ниже, мы создаем хранимую процедуру с именем uspGetEmployees. Я уверен, что вы можете догадаться, что подразумевает часть «Get Employees», но что такое «usp» в имени? Вы можете именовать хранимые процедуры как вам нравится, но отраслевым стандартом является использование соглашения, которое мы здесь применили. «usp» — это сокращение от «User Stored Procedure» (пользовательская хранимая процедура).
USE [AdventureWorks2019]
GO
CREATE PROCEDURE uspGetEmployees
AS
SELECT
BusinessEntityID
, FirstName
, LastName
FROM Person.Person;
GO
Видно, что блок кода выше очень похож на создание представления (VIEW) в SQL Server. Вы просто предваряете оператор SELECT текстом «CREATE PROCEDURE «.
Замечание. При создании хранимой процедуры вы можете сократить слово «PROCEDURE» до «PROC»; любой вариант будет работать.
Выполнение хранимых процедур
При выполнении хранимой процедуры вам просто нужно напечатать команду EXECUTE (или EXEC) с последующим именем хранимой процедуры, как в примере ниже.
EXEC uspGetEmployees;

Замечание. В большинстве случаев вам не нужно использовать команду EXEC для выполнения хранимой процедуры. Вы можете напечатать имя хранимой процедуры и выполнить ее. Вы получите одинаковые результаты в любом случае. Для простоты мы будем использовать команду EXEC на всем протяжении статьи.
Изменение/модификация хранимой процедуры
Вы можете использовать команду «ALTER PROC» или «ALTER PROCEDURE» для изменения функциональности хранимой процедуры. Скажем, мы хотим также получать отчество из таблицы «Person.Person» в базе данных AdventureWorks. Вместо создания новой хранимой процедуры мы можем модифицировать имеющуюся, как в примере ниже.
ALTER PROC uspGetEmployees
AS
SELECT
BusinessEntityID
, FirstName
, MiddleName
, LastName
FROM Person.Person;
GO
Давайте выполним хранимую процедуру снова и посмотрим на результаты.
EXEC uspGetEmployees;

Видно, что единственным отличием в результатах этого множества и предыдущего является наличие отчества.
ОК, довольно просто. Давайте продолжим. Теперь мы добавим предложение WHERE и будем использовать предикат IS NOT NULL для фильтрации сотрудников, у которых присутствует отчество (не стоит NULL).
ALTER PROC uspGetEmployees
AS
SELECT
BusinessEntityID
, FirstName
, MiddleName
, LastName
FROM Person.Person
WHERE MiddleName IS NOT NULL;
GO
Давайте опять выполним эту процедуру и посмотрим на результаты.
EXEC uspGetEmployees;

Удаление хранимой процедуры
Удаление хранимой процедуры в SQL подобно удалению таблицы, представления и т.д. Вы просто вызываете команду «DROP PROCEDURE» или «DROP PROC» с именем процедуры.
DROP PROC uspGetEmployees;
Если хранимая процедура не используется, она почти не потребляет ресурсов, и нет особых причин удалять их только потому, что вы хотите освободить ресурсы. Если вы сомневаетесь в необходимости удаления хранимой процедуры, создайте шаблон с содержимым хранимой процедуры, чтобы вам не пришлось переписывать весь этот код позднее.
Входные параметры хранимой процедуры
Что такое входной параметр? В простейшем виде это переменная. Когда мы добавляем переменную к хранимой процедуре, мы считаем ее входным параметром, а не переменной, хотя это одно и то же. Просто считайте это соглашением о наименовании, чтобы отделить одно от другого.
Когда мы добавляем входной параметр в хранимую процедуру, она помещается после команды «CREATE PROC» и выше команды «AS». И, как и для любой переменной, имя параметра должно начинаться с символа @ и последующим типом данных. В следующем примере мы изменяем нашу хранимую процедуру, добавляя переменную и присваивая ей значение по умолчанию.
ALTER PROCEDURE uspGetEmployees
@lastname VARCHAR(40) = 'abel'
AS
SELECT
BusinessEntityID
, FirstName
, LastName
FROM Person.Person
WHERE LastName = @lastname;
GO
Давайте выполним хранимую процедуру и посмотрим на результаты.
EXEC uspGetEmployees;

Что если я захочу вызвать процедуру с другим именем в качестве параметра? Тогда вам просто нужно указать эту переменную после имени хранимой процедуры, как в примере ниже. Тогда будет использоваться указанное значение вместо значения по умолчанию.
EXEC uspGetEmployees 'Akers';

Что если вы не уверены, как пишется имя? Хорошо, тогда опять изменим нашу хранимую процедуру. Теперь мы заменим равенство » sql»>ALTER PROCEDURE uspGetEmployees
@lastname VARCHAR(40) = ‘abel’
AS
SELECT
BusinessEntityID
, FirstName
, LastName
FROM Person.Person
WHERE LastName LIKE @lastname;
GO
Это позволит нам использовать подстановочные знаки в параметре и вернуть, например, каждого, чья фамилия начинается с «Ab».
EXEC uspGetEmployees 'Ab%';

Хранимые процедуры с несколькими входными параметрами
Добавление нескольких параметров следует тем же принципам, что и команда с единственным параметром, только они должны разделяться запятой. В примере ниже мы добавляем параметр «FirstName» и ссылку на него в предложении WHERE. Мы начинаем с удаления значений по умолчанию, которые присваивались параметрам в предыдущих примерах. Значения параметров будут передаваться пользователем при вызове хранимой процедуры.
ALTER PROCEDURE uspGetEmployees
@lastname VARCHAR(40)
, @firstname VARCHAR(40)
AS
SELECT
BusinessEntityID
, FirstName
, LastName
FROM Person.Person
WHERE LastName LIKE @lastname AND FirstName LIKE @firstname;
GO
Теперь при выполнении хранимой процедуры нам нужно включать в вызов значения обоих параметров. В этом примере мы вернем все строки, у которых фамилия начинается с «Ab», а имя — с «K».
EXEC uspGetEmployees 'Ab%', 'K%';

Когда используются несколько параметров, их значения при выполнении хранимой процедуры должны передаваться в том же порядке, в каком он перечислялись при создании хранимой процедуры. Однако это можно обойти. В нашем примере, если я захочу изменить порядок имени и фамилии и указать имя перед фамилией при выполнении процедуры, я могу это сделать путем указания имен переменных, как в примере ниже.
EXEC uspGetEmployees @FirstName = 'Kim', @LastName = 'Ab%';

Обычно вы не будете этого делать. Но если вы не уверены в порядке перечисления параметров, вы можете использовать этот метод, чтобы гарантировать получение желаемых результатов. В противном случае, если вы поменяете порядок параметров при вызове хранимой процедуры без указания имен параметров, запрос не вернет правильный результат. Поскольку сначала будет выполняться поиск по фамилии, а затем по имени — в порядке нашего примера, то запрос ничего не вернет, т.к. нет сотрудника в таблице с фамилией Kim и именем Abercrombie.
Выходные параметры хранимой процедуры
Выходные параметры чуть сложнее. Мы начнем с очень простого примера, чтобы вы могли понять суть создания выходного параметра хранимой процедуры. Блок кода ниже вы можете легко скопировать и вставить в свой редактор запросов. В этом примере я добавил номера строк, чтобы было легче ссылаться при объяснении каждого шага в примере с выходными параметрами.
1) CREATE PROC myRowCount
2) @lname VARCHAR(40),
3) @numrows INT = 0 OUTPUT
4) AS
5) SELECT LastName
6) FROM Person.Person
7) WHERE LastName LIKE @lname
SET @numrows = @@ROWCOUNT;
- Мы создаем новую хранимую процедуру с именем «myRowCount».
- Добавляем входной параметр с именем «@lname» и типом данных «VARCHAR(40)».
- Теперь давайте добавим выходной параметр с именем «@numrows», т.к. мы хотим подсчитать число строк, возвращаемых хранимой процедурой. Тип данных переменной — INT, значение по умолчанию — 0, и нам необходимо указать, что это выходной параметр, с помощью ключевого слова OUTPUT. Это даст знать SQL, что эта переменная будет использоваться для вывода данных.
- Ключевое слово «AS» говорит создать хранимую процедуру, содержащую последующий код.
- Наш основной оператор SELECT.
- Предложение FROM указывает таблицы, откуда будут извлекаться данные.
- Предложение WHERE устанавливает фильтры для оператора SELECT.
- На этой строке мы устанавливаем (присваиваем) для «@numrows» значение «@@ROWCOUNT». Когда вы видите двойной символ @, это признак системной переменной, которых имеется несколько. Познакомиться с системными переменными можно в документации. Поскольку @@ROWCOUNT получает значение при каждом выполнении запроса, мы захватим число, сгенерированное при выполнении нашей хранимой процедуры, и загрузим его в выходной параметр @numrows.
1) DECLARE @retrows INT
2) EXEC myRowCount 'B%', @numrows = @retrows OUTPUT
3) SELECT @retrows AS 'Rows';
- Здесь мы объявляем переменную, чтобы принять значение из выходного параметра процедуры.
В этом примере мы называем новую переменную «@retrows», сокращение от «return rows». Мы должны назначить ей тип данных, который соответствует выходной переменной, которую мы создали в хранимой процедуре. - Здесь мы выполняем нашу хранимую процедуру с помощью команды «EXEC myRowCount», и нам нужно предоставить значение входному параметру. В данном примере мы хотим вернуть все строки, для которых фамилия начинается с «B». Далее на той же строке мы хотим получить значение в нашу переменную «@numrows», которую мы создали в хранимой процедуре, и передать это значение в нашу новую переменную, которую мы описали в операторе DECLARE выше. Опять же нам нужно добавить ключевое слово OUTPUT, чтобы напомнить компилятору об этой переменной.
- Теперь мы создаем последний оператор SELECT, в котором получаем значение (число строк) из переменной «@retrows» и именуем результирующий набор «Rows».

Обработка ошибок в хранимых процедурах
Здесь мы опять изменим хранимую процедуру uspGetEmployees. С помощью функции @@ROWCOUNT, о которой говорилось в предыдущем разделе, мы будем возвращать сообщение «No Records Found», если ничего не будет возвращено. Это упрощенный, но эффективный взгляд на обработку ошибок в хранимых процедурах.
Здесь мы выполним хранимую процедуру дважды. Сначала вернем все строки, фамилии которых начинаются с «Ak»; потом выполним хранимую процедуру, которая вернет сообщение об ошибке. Но первое, что нужно сделать, — это изменить процедуру.
ALTER PROCEDURE uspGetEmployees
@lastname VARCHAR(40)
AS
SELECT
BusinessEntityID
, FirstName
, LastName
FROM Person.Person
WHERE LastName LIKE @lastname
IF @@ROWCOUNT < 1
BEGIN
RAISERROR('No Records Found', 10,1)
END;
GO
Теперь давайте выполним нашу хранимую процедуру для поиска фамилий, которые, как мы знаем, есть в таблице Person.Person.
EXEC uspGetEmployees 'Ak%';

Теперь выполним ту же процедуру с именем, которого нет в таблице Person.Person.
EXEC uspGetEmployees 'zz%';

Несмотря на то, что это был примитивный пример, он даст вам направление работы в тестовом и рабочем окружении. Экспериментируйте и вносите изменения. Делайте обработку ошибок интуитивно понятной.
Безопасность хранимых процедур
- Абстракция: Пользователи могут выполнять сложные запросы, не зная структуры лежащих в основе таблиц или других объектов базы данных.
- Шифрование: Мы можете зашифровать код в хранимой процедуре после ее создания.
- Предотвращение SQL-инъекции: Все передаваемые в хранимую процедуру параметры могут быть проверены до передачи их в таблицу или другой объект базы данных.
- Согласованное манипулирование данными: Хранимая процедура выполняет всякий раз один и тот же код в одном и том же порядке.
- Контроль выполнения: Вы можете установить разрешения на хранимую процедуру, позволяя только определенным пользователям ли группам выполнять ее.
- Обработка ошибок: Хранимые процедуры обеспечивают возможность использовать последовательную и эффективную обработку ошибок и создание отчетов.
Ссылки по теме:
Обратные ссылки
Нет обратных ссылок
Комментарии
Показывать комментарии Как список | Древовидной структурой
Автор не разрешил комментировать эту запись
Хранимые функции. За и против
Использование хранимых функций СУБД для реализации бизнес-логики или её части, всегда было камнем преткновения. С одной стороны баррикад DBA и программисты БД, с другой — разработчики backend.
Рискну навлечь на себя гнев из обоих лагерей, но всё же просуммирую плюсы и минусы и изложу свои соображения о том, когда стоит писать код в хранимых функциях, а когда следует выносить наружу.
Начнём с аргументов против:
Размазывание бизнес-логики
Это, на самом деле не проблема СУБД и ХФ, как инструмента — это проблема их неверного использования. У программиста бд может возникнуть желание описать всю логику реализуемого действия в хранимой функции — действительно, ведь все данные вот они, под рукой. Если программист поддастся на искушение, а его руководитель не возразит, в будущем могут возникнуть проблемы с узостью интерфейса со внешней системой (например, с сервером приложений) — придётся добавлять новые параметры, усложнять логику и т.п. Это даже может привести к тому, что появятся «дублирующие» ХФ со слегка иным функционалом.
Скудность языка СУБД
Есть такое дело. Традиционные языки для написания ХФ pl/sql, t-sql, pl/pgsql довольно примитивны по сравнению с современными языками общего назначения. Стоит заметить, что есть возможность писать ХФ и на более продвинутых языках, например Java в Oracle или Python в postgresql.
Непереносимость хранимых функций
Имеется в виду несовместимость диалектов процедурных языков разных СУБД. Многоплатформенность как раз на уровне — благодаря поддержке разных ОС и архитектур в самих СУБД и независимости встроенных языков от внешней платформы. Здесь опять решение зависит от специфики проекта. Если проект тиражируемый, причём вы не контролируете платформу (классический пример — CMS), то переносимость вам необходима и использование ХФ — только добавит головной боли. Если же проект уникальный, либо внедрения будут происходить унифицировано (например в разных филиалах одной компании), то про непереносимость между разными СУБД можно забыть.
Отсутствие необходимых навыков у команды и высокая «стоимость» соответствующих специалистов
Это, на мой взгляд, самый серьёзный аргумент против использования ХФ. Тут всё зависит от масштабов проекта. Грубо говоря, использование хранимого кода на стороне СУБД оправдано в средних-крупных enterprise проектах. Если проект помельче — овчинка выделки не стоит. Если проект огромный сверхнагруженный, то архитектура с ХФ и РСУБД упрётся в проблемы масштабирования — тут необходимо использование специфического хранилища и подхода к обработке данных.
Скорость
При обработке даже небольших объёмов данных во внешнем приложении мы тратим дополнительное время на передачу по сети и преобразование данных в нужный нам формат. К тому же в СУБД уже встроены, отлажены и протестированы близкие к оптимальным алгоритмы обработки данных, вашим программистам незачем практиковаться в изобретении велосипедов.
Сокрытие структуры данных
С ростом и эволюцией программной системы схема данных может и должна меняться. Хорошо спроектированный программный интерфейс на ХФ позволит менять схему данных не изменяя код внешних приложений (которых может быть несколько). Отсюда органично вытекает и разделение ролей разработчиков, которые работают с БД и знают её структуру, и разработчиков внешних приложений, которые должны знать лишь предоставляемый API. При использовании динамического SQL на стороне приложения, для подобного разделения вводятся дополнительные слои программных абстракций БД, различные ORM.
Гибкое управление правами доступа
Хорошей практикой является ограничение пользователя, под которым «ходит» в базу клиентское приложение в правах таким образом, что он не имеет прав на чтение и изменение никаких объектов. Лишь выполняет разрешённые ему функции. Таким образом можно жёстко контролировать какие действия доступны клиенту, уменьшается вероятность нарушения целостности данных из-за ошибки клиентского приложения.
Меньшая вероятность SQL injection
При использовании динамического SQL со стороны клиентской программы, клиентская программа передаёт СУБД SQL команды в виде строк, предварительно формируемых в коде. При формировании этих строк программисту нужно быть предельно внимательным, чтобы не допустить возможности непредусмотренной модификации SQL команды. При использовании ХФ SQL код на стороне приложения обычно статический, и выглядит, как простой вызов ХФ, параметры которой передаются не строками, а через placeholders (:variable) через механизм binding. Конечно это не исключает возможность SQL injection полностью (ведь можно умудриться в ХФ конкатенировать строку, переданную параметром с текстом динамически выполняемого SQL запроса), но значительно уменьшает её вероятность.
Повторное использование SQL
Реализуя логику работы с данными в хранимом слое мы получаем привычную нам иерархическую модель повторного использования SQL кода.
При использовании динамического SQL повторное использование запросов затруднено.
Например пусть есть система A на базе ХФ и система Б на базе динамического SQL. В обеих системах есть функция получения цены товара get_price. В случае A — это хранимая функция или отображение (view), в случае Б, допустим, процедура на java, через JDBC выполняющая SQL запрос. Есть задача — получить общую стоимость товара на складе. В случае A мы джоиним get_price прямо в запрос, получающий список товаров на складе (в случае, если get_price — view или ХФ на SQL, как например в PostgreSQL, то оптимизатор разворачивает запрос inline — тем самым получается один запрос, который быстро находит сумму).
В случае B есть два варианта — либо пробежать по курсору с выборкой товаров на складе и n раз вызвать get_price (а это значит что вся выборка должна передаться по сети на клиент) либо забыть про повторное использование и написать подзапрос, дублирующий тот, что был уже написан в get_price. Оба варианта — плохие.
Простая отладка SQL
Упрощается отладка (по сравнению с разнородной процедурой внешний код+sql)
В системах с динамическим SQL (любые ORM) даже простая задача поиска проблемного куска SQL может оказаться сложной.
Семантическая и синтаксическая проверка SQL на этапе компиляции.
Возможность профилирования функций и поиска узких мест.
Возможность трассировки уже запущеной и работающей системы.
Автоматический контроль зависимостей — при изменении определения объекта инвалидируются зависимые сущности.
Когда писать бизнес-логику в БД?
Если важна скорость обработки данных
Обработка данных прямо на месте их хранения зачастую даёт значительный прирост скорости обработки. Становятся возможными такие оптимизации, как, например, агрегации на уровне хранилища данных — данные с массива даже не передаются на сервер СУБД, не говоря о клиенте.
Когда важна целостность и непротиворечивость данных
В хранимых функциях с явным управлением транзакциями и блокировками проще обеспечить целостность данных и атомарность операций. Конечно всё это может быть реализовано и снаружи, но это отдельная и большая работа.
Данные имеют сложную, но устоявшуюся структуру
Плоские и слабо взаимосвязанные структуры часто не требуют всего богатства инструментов обработки, которые предлагают СУБД. Для них можно использовать сверхбыстрые key-value хранилища и кеширование в памяти.
Сложно организованные сильно связанные иерархические и сетевые структуры — явный показатель, что ваши знания РСУБД пригодятся!
Когда выносить код наружу?
Работа с внешними данными
Если специфика системы такова, что данных, приходящих на обработку снаружи (с датчиков, из других систем) больше, чем данных, сохраняемых в БД, то многие плюсы БД, как платформы программирования теряются. Оказывается проще обработать поступающие даннные снаружи и сохранить результат в БД, чем сначала всё пихать в БД, а потом обрабатывать. Здесь соблюдается тот же принцип — обрабатывать данные как можно ближе к источнику, о котором мы говорили выше применительно обработке данных, уже хранящихся в БД.
Сложные алгоритмы
Сложные или высоко-оптимизированные алгоритмы-числодробилки лучше писать на более приспособленных для этого языках. Встроенные языки РСУБД очень мощны (в том смысле, что высокоуровневые, а не гибкие), но за счёт этого имеют высокий overhead.
Highload
В сверхвысоконагруженных системах обычные подходы к сериализации транзакций и синхронизации серверов кластера становятся узким местом. Для таких систем характерны уникальные решения под конкретные задачи, универсальные и мощные системы РСУБД часто оказываются слишком медлительными при нагрузках в сотни тысяч конкурентных транзакций в секунду.
Вывод такой, что чёткого алгоритма нет. Каждый раз решение остаётся за архитекторами и менеджером и от него зависит то, завязнет ли проект в проблемах с race conditions и неконсистентностью данных NoSQL, проблемах с производительностью и отладкой запросов ORM, или упрётся в проблемы масштабирования СУБД при использовании хранимых функций. Поэтому — принимайте верные решения 🙂
- хранимые процедуры
- oracle
- postgresql
- архитектура приложений
- субд
- базы данных
- Oracle
- Программирование
Хранимые процедуры
Нередко операция с данными представляет набор инструкций, которые необходимо выполнить в определенной последовательности. Например, при добавлении данных покупки товара необходимо внести данные в таблицу заказов. Однако перед этим надо проверить, а есть ли покупаемый товар в наличии. Возможно, при этом понадобится проверить еще ряд дополнительных условий. То есть фактически процесс покупки товара охватывает несколько действий, которые должны выполняться в определенной последовательности. И в этом случае более оптимально будет инкапсулировать все эти действия в один объект — хранимую процедуру (stored procedure).
То есть по сути хранимые процедуры представляют набор инструкций, которые выполняются как единое целое. Тем самым хранимые процедуры позволяют упростить комплексные операции и вынести их в единый объект. Изменится процесс покупки товара, соответственно достаточно будет изменить код процедуры. То есть процедура также упрощает управление кодом.
Также хранимые процедуры позволяют ограничить доступ к данным в таблицах и тем самым уменьшить вероятность преднамеренных или неосознанных нежелательных действий в отношении этих данных.
И еще один важный аспект — производительность. Хранимые процедуры обычно выполняются быстрее, чем обычные SQL-инструкции. Все потому что код процедур компилируется один раз при первом ее запуске, а затем сохраняется в скомпилированной форме.
Для создания хранимой процедуры применяется команда CREATE PROCEDURE или CREATE PROC .
Таким образом, хранимая процедура имеет три ключевых особенности: упрощение кода, безопасность и производительность.
Например, пусть в базе данных есть таблица, которая хранит данные о товарах:
CREATE TABLE Products ( Id INT IDENTITY PRIMARY KEY, ProductName NVARCHAR(30) NOT NULL, Manufacturer NVARCHAR(20) NOT NULL, ProductCount INT DEFAULT 0, Price MONEY NOT NULL );
Создадим хранимую процедуру для извлечения данных из этой таблицы:
USE productsdb; GO CREATE PROCEDURE ProductSummary AS SELECT ProductName AS Product, Manufacturer, Price FROM Products
Поскольку команда CREATE PROCEDURE должна вызываться в отдельном пакете, то после команды USE, которая устанавливает текущую базу данных, используется команда GO для определения нового пакета.
После имени процедуры должно идти ключевое слово AS.
Для отделения тела процедуры от остальной части скрипта код процедуры нередко помещается в блок BEGIN. END:
USE productsdb; GO CREATE PROCEDURE ProductSummary AS BEGIN SELECT ProductName AS Product, Manufacturer, Price FROM Products END;
После добавления процедуры мы ее можем увидеть в узле базы данных в SQL Server Management Studio в подузле Programmability -> Stored Procedures :

И мы сможем управлять процедурой также и через визуальный интерфейс.
Выполнение процедуры
Для выполнения хранимой процедуры вызывается команда EXEC или EXECUTE :
EXEC ProductSummary

Удаление процедуры
Для удаления процедуры применяется команда DROP PROCEDURE :
DROP PROCEDURE ProductSummary
