Oracle что значит nocopy
Многие из нас с появлением Oracle9i Database и далее Oracle10g Database начали активно разрабатывать приложения с помощью объектно-ориентированного PL/SQL. Однако вскоре выяснилось, что корпорация Oracle не полностью реализовала возможности присущие объектно-ориентированным языкам. В результате многие разработчики приложений на Oracle Database "охладели" к объектным возможностям PL/SQL.
В предлагаемой вашему вниманию статье предлагается ряд решений проблем, с которыми сталкиваются разработчики. Я уверен, что Oracle9i PL/SQL позволяет реализовывать развитую объектную модель, и, надеюсь, мое мнение разделит читатель.
Каждый раздел статьи сопровождается исходными текстами скриптов, демонстрирующими сответствующий подход. Все скрипты запускались и проверялись с помощью последней доступной на текущий момент версии Oracle10g Database - 10.1.0.2 Скрипты тестировались на следующей версии: Oracle 10.1.0.2 Enterprise Edition for linux x86 (Intel Xeon)
Вызов переопределенного метода в типе-потомке
С этой проблемой PL/SQL-программисты сталкиваются наиболее часто. Проблема связана с тем, что в PL/SQL отсутствует синтаксическая конструкция для вызова метода типа-предка. В случае порождения нового объектного типа от родительского, виртуальный метод переопределеяется, далее в этом порожденном типе нам необходимо вызвать данный метод типа-предка.
Например: пусть у нас есть класс t_ParentType в котором определен метод getName:
Теперь мы определяем объектный тип t_ChildType, который является наследником t_ParentType. В типе t_ChildType метод getName является виртуальным и переопределен. Для этого использовано ключевое слово OVERRIDING:
В реализации метода getName попытаемся вызвать унаследованный метод getName (объектного типа t_ParentType)
Таким образом, выясняется, что в PL/SQL нет синтаксической конструкции, для того чтобы сослаться на метод типа-предка.
В объектно-ориентированных языках для этого существуют специальные языковые конструкции. В Java это ключевое слово super (супер-класс), в Object Pascal - Inherited. Данный механизм обеспечивает доступ к унаследованной логике и устраняет избыточность кода. Документация по языку PL/SQL (Oracle10g Rel.1 PL/SQL User's Guide and Reference, Oracle10g Rel.1 Application Developer's Guide - Object-Relational Features) хранит по этому поводу молчание.
- Создается экземпляр типа-предка
- Затем копируется в него содержимое полей текущего экземпляра
- Происходит вызов нужного метода экземпляра типа-предка
- Поля экземпляра типа-предка копируются в текущий экземпляра.
Модифицируем исходный текст для реализации этого подхода, добавив в родительский тип копирующий конструктор:
В типе-потомке нам также будет необходим метод присваивания, который будет копировать все поля переменной экземпляра типа в текущий экземпляр, - назовем его assign. Далее добавим функцию inherited_getName, которая будет реализовывать алгоритм вызова функции getName родительского типа t_ParentType. Фактически метод inherited_getName представляет собой оболочку для метода getName типа-предка t_ParentType.
В заключение, можно заметить, что данная методика применима для иерархий типов произвольного числа уровней. Если же необходимо обеспечить вызов переопределенного метода из произвольного родительского класса, то нужно иметь уникальные имена для методов-оболочек.
Например: в следующей иерархии классов: t_ParentType -> t_ChildType -> t_SubChildType для вызова метода произвольного типа-предка можно использовать следующие правило: к имени метода добавляется цифра - номер уровня в иерархии. В этом случае имена методов-оболочек соответственно будут выглядеть следующим образом:
getName0->getName1->getName2
Вышеописанная методика демонстрируется в данном примере .
Наследование конструкторов
Очередная трудность связана с тем, что в PL/SQL не поддерживает прямой вызов унаследованного конструктора. (Проще говоря, конструкторы базового типа не наследуются!). Например: пусть у нас есть класс t_ParentType в котором определен пользовательский (user-defined) конструктор:
Теперь мы определяем объектный тип t_ChildType, который является наследником t_ParentType. В типе t_ChildType также определен пользовательский конструктор:
В реализации конструктора типа t_ChildType попытаемся вызвать унаследованный конструктор:
Выясняется, что сделать это не удается:
Итак: как же вызвать конструктор родительского типа, чтобы не дублирвать уже реализованный в нем код ?
Предлагается примерно тот же самый метод, что и в предыдущем разделе: создание экземпляра типа-предка, с последующим присвоением его полей полям текущего экземпляра. Для этого нам понадобится метод присвоения assign:
Реализация констант-атрибутов типа
Объектно-ориентированное расширение языка PL/SQL поддерживает статические методы типа, однако во многих случаях бывает необходимо использовать статические атрибуты класса, к сожалению PL/SQL не поддерживает такие поля. Нам бы хотелось иметь подобный код:
Увы, мы получаем ошибку:
Для реализации таких атрибутов можно использовать статический метод, который бы возвращал требуемое значение. Если значение атрибута также имеет объектный тип, то в качестве места хранения значения такого атрибута можно использовать вспомогательный пакет. Для защиты переменной от модификации необходимо поместить её объявление в тело пакета.
Следующий листинг реализует данный подход:
Следующий объектный тип реализует статический метод, который возвращает объект-константу:
Вышеописанная методика демонстрируется в данном примере .
Заключение
Мы рассмотрели методы решения наиболее часто встречающихся проблем при использовании объектно-ориентированных возможностей PL/SQL. Конечно, многие проблемы могут быть решены только самими разработчика корпорации Oracle. Например, отсутствие защищенных полей объектного типа (так называемых private-полей), отсутствие поддержки интерфейсов и т.д.
Будем надеяться, что в следующих версиях Oracle Database эти недоработки будут устранены.
Всегда используйте FORALL и BULK COLLECT для любых нетривиальных многострочных операций SQL (то есть операций, в которых задействовано более нескольких десятков строк). Всегда ищите возможность организовать кэширование данных. А для многих задач обработки данных следует активно рассмотреть использование конвейерных функций. Иначе говоря, некоторые приемы оптимизации настолько эффективны, что должны использоваться при любой возможности.
Другие приемы оптимизации работают только в специальных ситуациях. Например, замена типа данных INTEGER типом PLS_INSTEGER принесет сколько-нибудь заметную пользу только в программе, интенсивно обрабатывающей числовые данные.
Именно этой теме и посвящена настоящая статья: в ней рассматриваются функции PL/SQL, способные значительно повысить производительность, но только в особых ситуациях. В общем случае вам не стоит изначально беспокоиться о применении всех этих возможностей. Направьте усилия на разработку удобочитаемого, легкого в сопровождении кода, а если впоследствии в программе обнаружатся узкие места, посмотрите, не помогут ли вам какие-либо из описанных приемов.
Метод передачи параметров NOCOPY
Режим передачи параметров NOCOPY требует, чтобы ядро PL/SQL передавало аргументы IN OUT по ссылке, а не по значению. Передача по ссылке повышает производительность программ, потому что она предотвращает копирование аргументов в программный модуль. При передаче больших, сложных структур (коллекций, записей или объектов) копирование данных может быть сопряжено с заметными затратами.
Чтобы понять смысл режима NOCOPY и его возможное влияние на производительность, необходимо ознакомиться с тем, как PL/SQL передаются параметры. Существует два способа передачи:
- По ссылке. С соответствующим формальным параметром связывается указатель, а не фактическое значение. После этого и формальный и фактический параметры ссылаются на одну ячейку памяти, содержащую значение параметра.
- По значению. Значение фактического параметра копируется в соответствующий формальный параметр. Если впоследствии программа завершается без инициирования исключений, значение формального параметра присваивается фактическому. В случае же возникновения ошибки измененное значение обратно не передается. Без использования NOCOPY передача параметров в PL/SQL выполняется по следующим правилам.
Из этих определений и правил следует, что передача большой структуры в режиме OUT или IN OUT производится по значению, а копирование может привести к потере производительности и лишним затратам памяти. Для предотвращения всех этих неприятностей и служит секция NOCOPY . Она включается в объявление параметра следующим образом:
Секция NOCOPY используется только совместно с режимами OUT и IN OUT . Пример списка параметров с секцией NOCOPY :
Применяя секцию NOCOPY , следует учитывать два важных обстоятельства:
- Каждый раз при вызове подпрограммы с выходным параметром, объявленным с NOCOPY , значение фактического параметра, соответствующего выходному, устанавливается в NULL .
- Секция NOCOPY — это рекомендация, а не команда, поэтому не исключено, что компилятор самостоятельно примет решение о невозможности выполнения запроса. Ограничения на использование NOCOPY перечислены в следующем разделе.
Ограничения на использование NOCOPY
Иногда компилятор PL/SQL игнорирует секцию NOCOPY и использует метод передачи по значению, который задан для параметров с режимами OUT и IN OUT по умолчанию. Это может произойти в следующих ситуациях:
- Фактический параметр является элементом ассоциативного массива. Секция NOCOPY может применяться ко всей коллекции, но не к ее отдельным элементам. Данное ограничение легко обойти: скопируйте структуру в переменную, скаляр или запись, а затем передайте ее как параметр, объявление которого содержит секцию NOCOPY .
- Некоторые ограничения на фактические параметры. Под действием некоторых ограничений секция NOCOPY игнорируется. Например, к ним относится указание количества знаков в дробной части для числовых значений или ограничение NOT NULL . На строковые переменные, объявленные с ограничением длины, это не распространяется.
- Фактические и формальные параметры представляют собой записи. Одна или обе записи объявлены с использованием атрибута %ROWTYPE или %TYPE , а ограничения на соответствующие поля этих двух записей разные.
- Передача фактического параметра требует неявного преобразования типов данных. Выход из этой ситуации может быть следующим: поскольку всегда лучше использовать явное преобразование типов данных, выполните его, а затем передавайте преобразованные значения как параметры, в объявлении которых имеется секция NOCOPY.
- Обращение к подпрограмме осуществляется путем внешнего или удаленного вызова процедуры. В таких ситуациях PL/SQL всегда будет передавать фактические параметры по значению.
Эффективность NOCOPY
Итак, насколько же NOCOPY ускорит вашу программу? Чтобы ответить на этот вопрос, я создал пакет с двумя процедурами:
Каждая процедура удваивает значение элемента вложенной таблицы:
Затем для каждой процедуры:
- Вложенная таблица была заполнена 100 000 строк данных.
- Процедура была вызвана 1000 раз.
В Oracle Database 10g были получены следующие результаты:
Однако в Oracle Database 11g результаты были такими:
Я провел аналогичные тесты с коллекциями строк — с аналогичными результатами. После проведения многократных тестов я делаю вывод, что до Oracle Database 11g наблюдался значительный прирост производительности, а в Oracle Database 11g этот прирост существенно сократился. Как я полагаю, это объясняется общей оптимизацией ядра PL/SQL в новой версии.
Недостатки NOCOPY
В зависимости от приложения конструкция NOCOPY может повысить производительность программ с параметрами IN OUT и OUT . Однако за потенциальный прирост приходится расплачиваться: если программа завершается с необработанным исключением, вы не можете доверять значениям в фактическом параметре NOCOPY .
Что значит «доверять»? Давайте разберемся, что происходит в PL/SQL с параметрами при завершении программы с необработанным исключением. Предположим, я передаю своей процедуре calculate_totals запись IN OUT. Исполнительное ядро PL/ SQL сначала создает копию этой записи, а затем во время выполнения программы вносит изменения в копию. Фактический параметр не изменяется до того момента, когда процедура calculate_totals успешно завершится (без передачи исключения). В этот момент локальная копия переносится в фактический параметр, а программа, вызвавшая calculate_totals , может обратиться к измененным данным. Но если процедура calculate_totals завершится с необработанным исключением, вызывающая программа может быть уверена в том, что значение фактического параметра осталось неизменным.
С рекомендацией NOCOPY эта уверенность исчезает. При передаче параметра по ссылке (эффект NOCOPY ) любые изменения, вносимые в формальный параметр, также немедленно вносятся в фактический параметр. Предположим, программа calculate_totals читает коллекцию из 10 000 строк и вносит изменения в каждую строку. Если ошибка возникла в строке 5000 и была передана из calculate_totals необработанной, коллекция (фактический параметр) будет изменена только наполовину.
Файл nocopy.tst на сайте github демонстрирует проблемы с использованием NOCOPY . Запустите сценарий и разберитесь во всех тонкостях этого режима передачи параметров, прежде чем использовать его в своих приложениях.
В целом, будьте внимательны при использовании рекомендации NOCOPY . Используйте ее только тогда, когда вы твердо уверены в наличии проблем, связанных с передачей параметров; будьте готовы к возможным последствиям при инициировании исключений.
Руководитель разработки PL/SQL Брин Луэллин придерживается иных взглядов на NOCOPY : он рекомендует широко применять эту возможность в программах. Брин полагает, что побочные эффекты частично измененных структур данных не должны представлять большой опасности, потому что эта ситуация возникает только при появлении непредвиденных ошибок. В таких случаях почти всегда следует прервать работу приложения, сохранить информацию об ошибке и передать исключение во внешний блок. Тот факт, что коллекция находится в неопределенном состоянии, в этот момент уже не важен.
Выбор типа данных
При выполнении относительно небольшого количества операций не так уж важно, будет ли PL/SQL выполнять неявные преобразования или использовать относительно медленную реализацию. С другой стороны, для алгоритмов, сопряженных с большими объемами вычислений, следующие рекомендации могут обеспечить заметный выигрыш.
Избегайте неявных преобразований
PL/SQL , как и SQL , во многих ситуациях выполняет неявные преобразования. Так, в следующем блоке PL/SQL сначала преобразует целочисленное значение 1 в вещественный формат 1.0, прежде чем суммировать его с другим числом и присвоить результат переменной типа NUMBER :
Многие разработчики знают, что выполнение неявных преобразований в командах SQL может привести к снижению производительности. Неявные преобразования в PL/ SQL тоже могут повлиять на производительность, хотя и не так серьезно, как преобразования в SQL.
Чтобы проверить влияние неявных преобразований на производительность в вашей среде, запустите сценарий test_implicit_conversion.sql.
Используйте тип PLS_INTEGER для интенсивных целочисленных вычислений
Целочисленная переменная, объявленная с типом PLS_INTEGER , расходует меньше памяти по сравнению с INTEGER и выполняет свою работу более эффективно за счет использования аппаратной поддержки вычислений. В программе с интенсивной обработкой числовых данных даже простое изменение типа может заметно повлиять на производительность. Более подробная информация о разных целочисленных типах приведена в разделе «Тип PLS_INTEGER » вот этого блога.
используйте тип BINARY_FLOAT или BINARY_DOUBLE для интенсивных вещественных вычислений
В Oracle10g появились два новых вещественных типа, BINARY_FLOAT и BINARY_DOUBLE . Они соответствуют стандарту IEEE 754 и используют машинную реализацию математических операций, что делает их более эффективными по сравнению с NUMBER или INTEGER . За дополнительной информацией обращайтесь к разделу «Типы BINARY_FLOAT и BINARY_DOUBLE » этой статьи.
Оптимизация вызовов функций в SQL (версия 12.1 и выше)
Oracle Database 12c предоставляет два важных усовершенствования, повышающих эффективность вызова функций PL/SQL в командах SQL :
- Секция with function .
- Директива UDF .
Синтаксис WITH FUNCTION более подробно рассматривается в этом блоге.
Директива UDF предоставляет гораздо более простое средство повышения эффективности вызовов функций из SQL . Чтобы воспользоваться ею, достаточно добавить следующую строку в раздел объявлений функции:
Эта директива сообщает Oracle: «Эта функция будет в основном вызываться из SQL , а не из блоков PL/SQL ». Oracle использует эту информацию для сокращения затрат на переключения контекста с SQL на PL/SQL при выполнении этой функции.
В результате функция значительно быстрее выполняется из SQL (руководитель группы разработки PL/SQL утверждает, что возможно четырехкратное повышение производительности), но при выполнении из блока PL/SQL она будет работать чуть медленнее (!). Сценарий в файле 12c_udf.sql демонстрирует использование этой возможности. Он сравнивает производительность функций с включенным режимом UDF и без него. Проверьте сами и посмотрите, какой выигрыш может принести использование этой очень простой директивы!
Подведем итог: сначала попробуйте использовать директиву UDF . Если она не обеспечит заметного прироста скорости, тогда пробуйте WITH FUNCTION .
OR REPLACE – повторно создать программный модуль сохранив ранее предоставленные привилегии на его выполнение.
AUTHID – как будет выполняться программа и как должны разрешаться ссылки на объекты: DEFINER – в соответствии с привилегиями владельца объекта, либо CURRENT_USER – в соответствии с привилегиями пользователя вызывающего программу. По умолчанию считается DEFINER.
Примечание. Анонимные блоки и программы выполняются в соответствии с привилегиями пользователя вызывающего программу (CURRENT_USER).
DETERMINISTIC – (детерминированная программа) для одних и тех же входных значений возвращает один и тот же результат. Например, функция INITCAP является детерминированной, а SYSDATE – нет.
PARALLEL_ENABLE – указывает оптимизатору, что функция безопасна для параллельного выполнения. Предложение PARTITION BY доступно только для функций, имеющих параметр in_parm типа REF CURSOR IN, оно сообщает оптимизатору как распределяются входные данные между параллельными процессами.
PIPELINED – (конвейеризованная) применяется только к табличной функции, и означает, что результаты этой табличной функции должны возвращаться итеративно, с помощью команды PIPE ROW. Такая конвейерная функция может возвращать данные по мере их получения, а не все сразу после завершения всего процесса обработки
AGGREGATE USING – (агрегатное использование). Такая агрегатная функция работает с группой строк, а возвращает единственный результат. Например, функция вычисления среднего AVG, является агрегатной.
Список формальных параметров программы состоит из параметров разделённых запятыми. Синтаксис каждого параметра таков:
Тип данных может быть %TYPE, %ROWTYPE, NUMBER, DATE, либо текстовый, причём без ограничения по размеру.
По умолчанию параметр считается чисто входным IN.
Ключевое слово NOCOPY указывает, что параметр следует передавать по ссылке, а не по значению. Обычно параметры передаются по значению, при этом создаётся копия параметра и подпрограмма работает именно с копией данных. Однако, если параметр имеет большой объём, то его копирование может замедлить работу программы.
Ещё одной особенностью является то, что при возникновении исключения внутри подпрограммы, выходные параметры, передаваемые по значению (без NOCOPY) не будут изменены в вызывающей программе, а параметры, передаваемые по ссылке (с NOCOPY) будут иметь те значения, которые они получили к моменту возникновения исключения.
Ключевое слово DEFAULT (или :=) применяется только для входного параметра. Такой параметр можно не задавать при вызове программы, в этом случае неуказанный параметр получает значение заданное после ключевого слова DEFAULT (или :=).
Процедура может вызываться несколькими способами:
Как отдельная исполняемая инструкция:
PUT_LINE('No Employees Found') ;
По стандарту ANSI SQL с ключевым словом CALL:
CALL PUT_LINE('No Employees Found') ;
По обычаю PL/SQL с ключевым словом EXECUTE:
EXECUTE PUT_LINE('No Employees Found') ;
В общем виде синтаксис вызова хранимой функции таков:
[имя_схемы.][имя_пакета.]имя_функции[@БД_связь]
[(список параметров)]
Обратите внимание на то, что при совпадении имени схемы и имени пакета может возникать путаница, например, если вы создадите пакет scott с процедурой list, а в схеме scott есть процедура с именем list, то вызов функции scott.list будет неоднозначным. Вывод: называйте свои пакеты не тривиально (например, с префиксами – в стандартном пакете Oracle это “DBMS_”).
Функция может вызываться в любом месте программы, где допустимо использование выражения того же типа, а именно:
В операторе присваивания:
sales04 := tot_sales(2004, ‘C’);
При задании значения по умолчанию:
sales03 NUMBER DEFAULT tot_sales(2003, ‘C’);
В логическом выражении:
IF tot_sales(2002, ‘C’) > 10000 THEN
SELECT first_name, surname
FROM sellers
WHERE tot_sales(2001, ‘C’) > 1000
Как аргумент в списке параметров другой программы:
SELECT RTRIM(TO_CHAR(SYSDATE, ‘MONTH’)) || ‘ ’
|| TO_CHAR(SYSDATE, ‘DDTH’) FROM DUAL;
В PL/SQL имеет два способа передачи фактических параметров: по позиции или по имени.
Позиционная нотация используется по умолчанию. Каждое значение в списке аргументов связывается с параметром в соответствии с его относительной позицией. В этом случае входные аргументы, имеющие значения по умолчанию, можно опускать, если они расположены в конце списка.
Именованная нотация. Значение аргумента связывается с параметром явно по имени с помощью указания “=>”. В этом случае аргументы можно указывать в любом порядке, а входные параметры, имеющие значения по умолчанию, можно опускать, не зависимо от их положения в списке.
Можно сочетать оба варианта нотации, если позиционные аргументы находятся слева от именованных и в списке формальных параметров, и в списке фактических параметров.
Например, если объявлена процедура
CREATE OR REPLACE PROCEDURE hire_employee
(emp_id IN VARCAHAR2,
hire_date IN DATE := SYSDATE,
company_id IN NUMBER := 1
то о следующих вызовах можно сказать…
hire_employee(new_empno); -- правильно
hire_employee(new_empno,’12-Jan-99’); -- правильно
hire_employee(new_empno, ,2); -- НЕ правильно
-- Однако будет правильным такой вызов
hire_employee(new_empno, company_id=>2);
-- или такой вызов
hire_employee(company_id=>2, emp_id=>new_empno);
Перегрузка программы – механизм позволяющий определить несколько программ с одинаковым именем, но отличающихся сигнатурой, т.е. типом или числом параметров, либо типом программы (процедура или функция).
Например, в пакете DBMS определены следующие прототипы процедуры PUT_LINE обеспечивающие её перегрузку:
PUT_LINE(a VARCHAR2);
PUT_LINE(a NUMBER);
PUT_LINE(a DATE);
В результате можно обращаться к этой процедуре для печати данных любого типа.
Реально перегрузка используется только в пакетах, хотя может быть и в любом блоке.
Локальная программа – это процедура или функция, определённая в разделе объявлений блока PL/SQL. Область видимости и область действия этой программы ограничена данным блоком.
Хотя локальные программы могут быть в любом блоке, но реально используются только в пакетах, где обеспечивают сокрытие механизмов работы общедоступных процедур пакета.
Как и любой язык программирования, PL/SQL имеет свои особенности. Одни особенности делают язык программирования тем, что он есть, и программист использует их постоянно. А без других он может обходиться годами и даже вовсе не подозревать (или забыть) об их существовании.
Например, особенностью Java является необходимость объявлять исключения, которые выбрасывает метод. Без этого не обойтись. Другая особенность Java - невозможность использования одноименных переменных во вложенных блоках. Об этом вспоминаешь не часто. Очень разные вещи, но характерные для Java. А особенностью PL/SQL до версии Oracle 11g было отсутствие предложения CONTINUE - и для его имитации приходилось использовать безусловный переход на метку в конце тела цикла. Теперь эта особенность осталась в прошлом.
Недавно я предпринял систематический обзор PL/SQL по книге Study Guide for 1Z0-144: Oracle Database 11g: Program with PL/SQL: Oracle Certification Prep by Matthew Morris. Про некоторые особенности PL/SQL, которые привлекли мое внимание в процессе чтения, я сегодня и расскажу.
Доступ к переменной при помощи метки
В PL/SQL метки нужны не только для того, чтобы выполнять переход на помеченное предложение при помощи GOTO . Другое их назначение - давать имена блокам кода ( [DECLARE] BEGIN .. END ) и циклам ( [FOR | WHILE] LOOP .. END LOOP ), которые в отсутствие метки были бы анонимны. Имя, присвоенное блоку, может быть использовано как квалификатор для доступа к переменным этого блока.
Следующий пример демонстрирует доступ к одноименным переменным, определенным во вложенных блоках PL/SQL, из самого внутреннего блока. Доступ к переменной, определенной в секции объявлений процедуры, дает использование имени процедуры как квалификатора:
Русскоязычные имена меток и процедуры здесь напоминают о еще одной особенности PL/SQL (и SQL): в идентификаторах можно использовать не только латиницу, но все буквы из database character set - набора символов, установленных для базы данных Oracle при ее создании.
Удаляю следы эксперимента:
Непойманное исключение отменяет ранее сделанные изменения
Когда мы имеем дело с SQL, то ошибка при выполнении команды DML отменяет все изменения, которые данная команда успела сделать до того, как возникла ошибка. А результаты предыдущих команд DML, которые успешно выполнились в текущей транзакции, остаются в силе:
Отменю сделанные изменения и выполню те же три команды DML внутри анонимного PL/SQL блока:
Как видим, неперехваченное исключение приводит к отмене всех изменений, сделанных в блоке PL/SQL до того, как возникло исключение.
Анонимный блок, процедура или функция PL/SQL ведут себя в этом отношении одинаково: в результате их успешного выполнения мы получаем все изменения, сделанные командами DML, содержащимися в PL/SQL коде, а в результате их завершения с ошибкой (неперехваченное исключение) ни одно из сделанных изменений не сохраняется.
Похожее поведение - все или ничего - демонстрирует PL/SQL и в случае возвращения результатов процедуры через параметы OUT .
Удаляю следы эксперимента:
Передача значений через параметры OUT и IN OUT
Параметры с модификаторами OUT и IN OUT обычно передаются в вызываемую процедуру и обратно по значению, то есть,
- значения фактических параметров, с которыми вызывается процедура, копируются при вызове в параметры процедуры, и
- значения параметров процедуры, установленные в ходе ее работы, копируются обратно в фактические параметры при успешном завершении работы процедуры.
А если в ходе выполнения процедуры возникает исключение, то фактические параметры остаются без изменений:
Из примера видно следующее:
- Параметр p_out OUT в процедуре x инициализирован значением NULL перед выполнением кода процедуры - так всегда происходит с OUT параметрами. Значение фактического параметра l_out в вызванной процедуре недоступно через "внутренюю переменную" p_out . Тогда как значение переменной l_inout доступно в процедуре через параметр p_inout IN OUT .
- При аварийном завершении процедуры (в результате исключения) значения формальных параметров p_out и p_inout не копируются в формальные параметры l_out и l_inout .
Итак, PL/SQL заботливо предохраняет внешние по отношению к процедуре переменные от изменений вплоть до момента успешного завершения процедуры.
Платой за эту предосторожность является удваивание объема памяти, необходимого для хранения значений OUT и IN OUT параметров. Если эти параметры имеют составной тип данных (коллекция, запись, объект) и занимают много места в памяти, то их копирование при входе и выходе из процедуры неизбежно снижает производительность.
Подсказка (hint) NOCOPY рекомендует виртуальной машине PL/SQL передавать параметры в процедуру по ссылке, а не по значению, что не требует копирования. При этом код процедуры напрямую изменяет значения фактических параметров:
Как видим, подсказка NOCOPY была принята и процедура x , прежде чем завершилась аварийно, изменила значения переменных l_out и l_inout .
Инициализация глобальных переменных пакета
Как известно, глобальные, публичные и частные, переменные пакета, в т.ч. курсоры, сохраняют состояние в течение сеанса работы с СУБД Oracle. Однако, такое поведение можно отменить с помощью директивы компилятора PRAGMA SERIALLY_REUSABLE .
Вначале приведу пример с сохранением значений глобальных переменных. Виртуальная машина PL/SQL хранит их в области памяти PGA, связанной с текущим сеансом.
Как видим, все переменные были инициализированы при первом обращении к пакету и не изменялись при последующих обращениях, которые делались с интервалом в одну секунду.
Директива PRAGMA SERIALLY_REUSABLE инструктирует витруальную машину PL/SQL хранить глобальные переменные пакета не в PGA, а в специальной области SGA, где время жизни этих переменных ограничивается одним вызовом, а не длительностью сеанса:
Теперь каждый следующий вызов приводит к новой инициализации переменных пакета! Затрудняюсь сказать, для чего это нужно практически.
Читайте также: