
Полная версия:
Язык PL/SQL
3 l_at2 VARCHAR2(1);
4 BEGIN
5 SELECT at1,at2 INTO l_at1,l_at2 FROM tab1
6 WHERE at1 IN (1,2);
7 DBMS_OUTPUT.PUT_LINE(TO_CHAR(l_at1)||''||l_at2);
8 END;
9 /
DECLARE
*
ERROR at line 1:
ORA-01422: exact fetch returns more than requested number of rows
ORA-06512: at line 5
Если SQL-запрос команды SELECT INTO возвращает больше одной строки, то инициируется предопределенное исключение TOO_MANY_ROWS. Если возвращается пустая выборка, то инициируется другое предопределенное исключение – NO_DATA_FOUND. По этой причине команду SELECT INTO рекомендуется помещать в отдельный блок с обработкой этих исключений:
BEGIN
…
BEGIN
SELECT INTO …
EXCEPTION
WHEN TOO_MANY_ROWS THEN …
WHEN NO_DATA_FOUND THEN …
END;
…
END;
Команда SELECT INTO обычно используется тогда, когда есть уверенность, что ее SQL-запрос вернет ровно одну строку, например, для запроса строк таблицы с условием на значение ее первичного ключа.
Явные курсоры
Объявив SQL-запрос с помощью явного курсора, программист получает полный контроль над этапами его выполнения. Можно определить, когда открыть курсор (OPEN), когда считывать из него строки (FETCH) и когда закрыть курсор (CLOSE).
Объявим курсор cur1:
CURSOR cur1 IS SELECT at1,at2 FROM tab1;
Первым шагом работы с курсором является его открытие:
OPEN cur1;
Считывание строк результирующей выборки из курсора выполняется командой FETCH в набор переменных PL/SQL подходящих типов (число переменных должно совпадать с числом столбцов выборки):
FETCH cur1 INTO l_at1, l_at2;
Полностью код для получения трех строк из tab1 выглядит так:
SQL> DECLARE
2 CURSOR cur1 IS SELECT * FROM tab1;
3 rec tab1%ROWTYPE;
4 BEGIN
5 OPEN cur1;
6 FOR i IN 1..3 LOOP
7 FETCH cur1 INTO rec;
8 DBMS_OUTPUT.PUT_LINE(TO_CHAR(rec.at1)||' '||rec.at2);
9 END LOOP;
10 END;
11 /
1 A
2 B
3 C
PL/SQL procedure successfully completed.
После того, как курсор стал ненужным, его следует закрыть:
CLOSE cur1;
Если забыть закрыть явный курсор, как в приведенном выше примере, то можно считать, что запрограммирована утечка памяти в сервере Oracle. Вообще говоря, виртуальная машина PL/SQL автоматически сама закрывает и уничтожает открытые курсоры, как только они оказываются вне области видимости для выполняющегося в настоящий момент блока. Однако делается это не сразу, какое-то время такой курсор существует и остается открытым. У экземпляра Oracle есть ограничение на число одновременно открытых курсоров, которое задается параметром экземпляра open_cursors (по умолчанию параметр выставлен в 300). Если превысить значение этого параметра, то выполнение любого предложения SQL будет завершаться ошибкой. При параллельной работе большого числа сессий это весьма вероятно, поэтому чтобы не сталкиваться с ошибками такого вида, настоятельно рекомендуется аккуратно закрывать курсоры.
Программа, представленная выше, неудачна еще и тем, что цикл FOR со счетчиком предусматривает считывание конкретного числа строк, которых, вообще говоря, может и не быть в результирующей выборке. Строк в выборке может быть больше, чем указано (в данном случае у цикла FOR счетчик изменяется до 3) и тогда какие-то строки останутся несчитанными. Также возможна ситуация, когда число строк в выборке меньше значения счетчика – тогда произойдет повторное считывание последней строки.
Отметим, что повторное считывание из курсора последней строки выборки не приводит к ошибкам. Если в выборке, например, n строк, а команда FETCH выполнена k раз (k>n), то повторные считывания последней (n-й) строки не приведут к инициированию системных исключений, просто последняя строка выборки будет считана и выведена на экран несколько (k-n+1) раз. Для организации перебора строк результирующей выборки предназначены атрибуты явных курсоров, которые рассматриваются далее.
Объявление записей PL/SQL на основе курсоров
Переменная rec, в которую считывались строки результирующей выборки, была объявлена с помощью атрибута %ROWTYPE как запись PL/SQL на основе таблицы tab1. В данном случае это оправдано, потому что в SQL-запросе осуществляется выборка всех столбцов одной таблицы tab1 (SELECT * FROM tab1). Число атрибутов записи PL/SQL будет соответствовать числу столбцов строк выборки и считывание строк пройдет без ошибок.
Однако столбцы результирующей выборки могут быть не из одной, а из нескольких таблиц или вовсе могут являться выражениями:
CURSOR cur_short_person IS
SELECT born,
surname||' '||SUBSTR(name,1,1)||'.'||SUBSTR(secname,1,1)||'.' AS fio,
passport.seria||' '||passport.num AS passport_data
FROM person, passport
WHERE person.id=13243297
AND person.id=passport.r$person
В столбце fio результирующей выборки для каждой строки таблицы person будет результат выражения – фамилия и инициалы (например, Кислов Виктор Михайлович – Кислов В.М.). В столбце passport_data будут паспортные данные из таблицы passport и тоже выражением – серия и номер паспорта, «склеенные» через пробел.
Самый правильный способ определить то, во что будем «принимать» результирующую выборку SQL-запроса курсора – это объявить с помощью атрибута %ROWTYPE переменную-запись PL/SQL, основанную не на схеме одной таблицы, а прямо на курсоре. В этом случае список атрибутов записи PL/SQL по числу столбцов результирующей выборки будет сформирован автоматически.
l_short_person cur_short_person%ROWTYPE;
OPEN cur_short_person;
FETCH cur_short_person INTO l_short_person;
DBMS_OUTPUT.PUT_LINE('ФИО: '||l_short_person.fio);
DBMS_OUTPUT.PUT_LINE('Дата рождения: '||TO_CHAR(l_short_person.born));
DBMS_OUTPUT.PUT_LINE('Паспорт: '||l_short_person.passport_data);
В приведенном коде считывание строки результирующей выборки в запись PL/SQL осуществляется одной короткой командой FETCH без указания столбцов. При появлении новых столбцов во фразе SELECT запроса курсора новые атрибуты также автоматически появятся в записях PL/SQL, объявленных на основе курсора. Таким образом, объявление записей PL/SQL на основе курсоров позволяет писать компактный, поддерживаемый и расширяемый код.
Атрибуты явного курсора
Для управления считыванием строк из явных курсоров используются их атрибуты. В частности, они позволяют выполнить считывание для последующей обработки в программе всех строк результирующей выборки.
Таблица 3. Атрибуты явного курсора.
Атрибут курсора
Описание атрибута
%FOUND
TRUE, если из курсора считана очередная строка
%NOTFOUND
FALSE, если из курсора считана очередная строка
%ROWCOUNT
количество считанных до настоящего момента строк
%ISOPEN
TRUE, если курсор открыт
Основная нагрузка при считывании всех строк результирующей выборки ложится на атрибуты курсора %NOTFOUND и %FOUND, которые всегда находятся в связке – либо принимают противоположные логические значения TRUE и FALSE, либо оба UNKNOWN.
Атрибут %FOUND равен TRUE и атрибут %FOUND равен FALSE в то время, пока команда FETCH считывает из курсора все новые и новые (очередные) строки. После того, как последняя строка результирующей выборки будет считана дважды (второй раз, выходит, уже не как очередная), атрибут курсора %FOUND станет FALSE, а %NOTFOUND станет TRUE. На этом поведении атрибутов курсора обычно и формируется условие выхода из циклов, предназначенных для считывания из курсора всех строк результирующей выборки.
Еще одним важным фактом является то, что после открытия курсора, но до выполнения первой команды FETCH, атрибуты %FOUND и %NOTFOUND имеют неопределенное логическое значение (UNKNOWN). Если это не учитывать, то можно совершить одну из распространенных ошибок – в цикле WHILE с условием на истинность атрибута %FOUND цикл не будет выполнен ни разу, несмотря на то, что в результирующей выборке есть строки. Выполнить команду FETCH первый раз надо еще до входа в цикл, тем самым проинициализировав атрибуты курсора.
Приведем пример использования атрибутов курсора в цикле WHILE для считывания всех строк результирующей выборки.
SQL> DECLARE
2 CURSOR cur1 IS SELECT * FROM tab1;
3 rec cur1%ROWTYPE;
4 BEGIN
5 OPEN cur1;
6 FETCH cur1 INTO rec;
7 WHILE cur1%FOUND LOOP
8 DBMS_OUTPUT.PUT_LINE(TO_CHAR(rec.at1)||' '||rec.at2);
9 FETCH cur1 INTO rec;
10 END LOOP;
11 CLOSE cur1;
12 END;
13 /
1 A
2 B
3 C
PL/SQL procedure successfully completed.
Еще один пример показывает использование атрибутов курсора для считывания всех строк выборки в простом цикле LOOP END LOOP с условием выхода EXIT WHEN.
SQL> DECLARE
2 TYPE tab1_rec_type IS RECORD
3 (arg1 tab1.at1%TYPE,
4 arg2 tab1.at2%TYPE);
5 tab1_rec tab1_rec_type;
6 CURSOR cur1 IS SELECT * FROM tab1;
7 BEGIN
8 OPEN cur1;
9 LOOP
10 EXIT WHEN (cur1%NOTFOUND);
11 FETCH cur1 INTO tab1_rec;
12 DBMS_OUTPUT.PUT_LINE(cur1%ROWCOUNT||' '||tab1_rec.arg2);
13 END LOOP;
14 CLOSE cur1;
15 END;
/
1 A
2 B
3 C
3 C
PL/SQL procedure successfully completed.
Обратите внимание на повторный вывод последней строки (3 С). Это еще одна распространенная ошибка. В ходе проведения занятий со студентами авторы десятки раз видели считывание и обработку последней строки выборки дважды. Системное исключение при повторном считывании последней строки выборки, напомним, не инициируется, поэтому такие ошибки в коде трудно обнаруживаются.
Рекомендуется после написания кода, реализующего считывание и обработку всех строк выборки, проверить его с помощью небольших тестов на отсутствие двух распространенных ошибок:
цикл считывания не выполняется ни разу;
последняя строка выборки в цикле обрабатывается дважды.
В приведенном выше примере показано, что значение %ROWCOUNT увеличивается на единицу с каждой считанной строкой, а не отражает общее число отобранных SQL-запросом строк. Видно и что повторное считывание последней строки выборки не влияет на значение атрибута %ROWCOUNT: оно остается равным значению, присвоенному при первом считывании последней строки. В примере значение атрибута %ROWCOUNT как стало равным трем при первом считывании последней строки, так и осталось без изменений после еще одного считывания.
Курсорный цикл FOR
Курсорный цикл FOR позволяет в цикле обработать все строки результирующей выборки SQL-запроса.
SQL> DECLARE
2 CURSOR cur1 IS SELECT at1,at2 FROM tab1;
3 v1 VARCHAR2(4000);
4 BEGIN
5 FOR rec IN cur1 LOOP
6 v1:=LTRIM(v1||' '||rec.at2);
7 END LOOP;
8 DBMS_OUTPUT.PUT_LINE(v1);
9 END;
10 /
A B C
PL/SQL procedure successfully completed.
Обратите внимание, переменная rec, в которую в цикле считываются данные, не требует объявления. Она будет являться записью PL/SQL, такой же, как записи PL/SQL, объявленные с помощью атрибута %ROWTYPE на основе курсора.
Все очень просто. Не нужно явно открывать и закрывать курсор. Вместо команды FETCH просто следует обратиться к текущему значению записи PL/SQL, которая здесь является своеобразной управляющей переменной цикла. Для выхода из цикла больше не нужно проверять атрибуты курсора %NOTFOUND и %FOUND. Если SQL-запрос не отберет ни одной строки, тело цикла просто не выполнится ни разу, если же результирующая выборка непустая, то после перебора всех строк цикл завершится автоматически.
По сути, программист тремя строчками кода говорит компилятору PL/SQL «Мне нужна каждая строка результирующей выборки, и я хочу, чтобы она была помещена в запись PL/SQL, соответствующую курсору». Компилятор PL/SQL формирует соответствующий байт-код со всеми низкоуровневыми вызовами сервера.
Простейший вариант курсорного цикла FOR имеет SQL-запрос, встроенный прямо в описание цикла:
SQL> DECLARE
2 v1 VARCHAR2(4000);
3 BEGIN
4 FOR rec IN (SELECT at1,at2 FROM tab1) LOOP
5 v1:= v1||' '||rec.at2;
6 END LOOP;
7 DBMS_OUTPUT.PUT_LINE(LTRIM(v1));
8 END;
9 /
A B C
PL/SQL procedure successfully completed.
Как и для неявных курсоров, для курсорного цикла FOR компилятор сам разместит в байт-коде низкоуровневые вызовы открытия курсора, считывания из него строк и закрытия.
Параметры курсора
Объявление курсора может содержать параметрический запрос, значения параметров которого передаются при открытии курсора. Рассмотрим соответствущий пример.
SQL> SELECT * FROM tab1;
AT1 A
– -
1 A
2 B
3 C
SQL> DECLARE
2 CURSOR cur2 (i INTEGER) IS SELECT * FROM tab1 WHERE at1>=i;
3 cur2_rec cur2%ROWTYPE;
4 BEGIN
5 OPEN cur2(2); – курсор открыт с параметром i, равным 2
6 FETCH cur2 INTO cur2_rec;
7 WHILE cur2%FOUND LOOP
8 DBMS_OUTPUT.PUT_LINE(cur2_rec.at1);
9 FETCH cur2 INTO cur2_rec;
10 END LOOP;
11 CLOSE cur2;
12 END;
13 /
2
3
PL/SQL procedure successfully completed.
Помимо явных курсоров параметризировать можно и команды SELECT INTO и курсорные циклы FOR. Для этого в коде SQL в качестве параметров надо использовать ранее объявленные переменные PL/SQL скалярных типов данных. При препроцессинге эти переменные будут автоматически заменены компилятором PL/SQL на связываемые переменные SQL.
Добавление, изменение и удаление данных
В исходном коде программы PL/SQL можно указывать команды добавления, изменения и удаления данных, для которых компилятор формирует в байт-коде вызовы соответствующих предложений SQL – INSERT, UPDATE и DELETE.
SQL> CREATE TABLE tab1 (at1 integer);
Table created.
SQL> DECLARE
2 l_at1 tab1.at1%TYPE;
3 BEGIN
4 l_at1 := 1;
5 INSERT INTO tab1 VALUES (l_at1);
6
7 l_at1 := 2;
8 INSERT INTO tab1 VALUES (l_at1);
9
10 l_at1 := 3;
11 INSERT INTO tab1 VALUES (l_at1);
12 DELETE FROM tab1 WHERE at1=1;
13
14 UPDATE tab1 SET at1=at1+1 WHERE at1=l_at1;
15
16 END;
17 /
PL/SQL procedure successfully completed.
SQL> SELECT * FROM tab1;
AT1
–
2
4
Используемые для выполнения команд INSERT, UPDATE, DELETE неявные курсоры тоже имеют атрибуты. Чтобы получить их значения, следует указывать имя курсора SQL%.
Таблица 4. Атрибуты неявного курсора.
SQL%FOUND
возвращает TRUE, если хотя бы одна строка была обработана DML-предложением SQL
SQL%NOTFOUND
возвращает TRUE, если ни одной строки не было обработано
SQL%ROWCOUNT
возвращает количество обработанных строк
SQL%ISOPEN
для неявных курсоров всегда возвращает FALSE, поскольку Oracle закрывает и открывает их автоматически
Эти атрибуты относятся к последнему использовавшемуся в программе неявному курсору, независимо от того, в каком блоке этот курсор использовался. До выполнения в программе первой команды PL/SQL с использованием неявного курсора атрибуты курсора с именем SQL% остаются неинициализированными (имеют значения UNKNOWN и NULL).
Наиболее часто используются атрибуты SQL%FOUND и SQL%ROWCOUNT, которые позволяют получить информацию о результатах обработки данных – сколько строк было обработано (добавлено, изменено или удалено) и были ли они вообще.
Приведем пример использования атрибутов неявных курсоров.
SQL> DECLARE
2 l_at1 tab1.at1%TYPE;
3 BEGIN
4
5 l_at1 := 1;
6
7 INSERT INTO tab1 VALUES (l_at1);
8 DBMS_OUTPUT.PUT_LINE(SQL%ROWCOUNT);
9
10 INSERT INTO tab1 SELECT * FROM tab1;
11 DBMS_OUTPUT.PUT_LINE(SQL%ROWCOUNT);
12
13 INSERT INTO tab1 SELECT * FROM tab1;
14 DBMS_OUTPUT.PUT_LINE(SQL%ROWCOUNT);
15
16 UPDATE tab1 SET at1=2 WHERE at1=1;
17 IF SQL%FOUND THEN
18 DBMS_OUTPUT.PUT_LINE('Строки изменялись');
19 ELSE
20 DBMS_OUTPUT.PUT_LINE('Строки не изменялись');
21 END IF;
22
23 END;
24 /
1
1
2
Строки изменялись
PL/SQL procedure successfully completed.
Возможность с помощью атрибутов неявных курсоров определять, были ли изменены данные после выполнения предложений SQL, используется, например, при реализации в программах PL/SQL оптимистической стратегии многопользовательского доступа.
Рассмотрим еще две возможности языка PL/SQL: конструкцию RETURNING и использование записей PL/SQL в DML-командах. Эти возможности наглядно иллюстрируют удобство использования языка PL/SQL при работе с базами данных Oracle.
Конструкция RETURNING
Конструкция RETURNING позволяет получить новые значения данных в таблицах после их добавления или изменения. Например, после увеличения оклада сотрудника на 10% в дальнейших вычислениях в коде может понадобиться новое значение оклада. Конечно, можно сразу после изменения выполнить выборку данных по этому сотруднику, но это будет еще одна операция, на которую потребуются дополнительные расходы ресурсов. Конструкция RETURNING позволяет их избежать.
Конструкцию RETURNING часто используют для получения значения первичного ключа после добавления новой строки в таблицу с использованием последовательности.
SQL> CREATE SEQUENCE sq1 START WITH 1 INCREMENT BY 2;
Sequence created.
SQL> DECLARE
2 l_at1 tab1.at1%TYPE;
3 BEGIN
4 INSERT INTO tab1 VALUES(sq1.NEXTVAL) RETURNING at1 INTO l_at1;
5 DBMS_OUTPUT.PUT_LINE(l_at1);
6 INSERT INTO tab1 VALUES(sq1.NEXTVAL) RETURNING at1 INTO l_at1;
7 DBMS_OUTPUT.PUT_LINE(l_at1);
8 END;
9 /
1
3
PL/SQL procedure successfully completed.
Использование записей PL/SQL в DML-командах
В DML-командах языка PL/SQL можно использовать и параметры-записи PL/SQL:
для указания того, что в команде UPDATE следует изменить целиком строку таблицы, используется ключевое слово ROW;
в команде INSERT после ключевого слова VALUES вместо списка переменных скалярных типов со значениями всех столбцов добавляемой строки указывается одна переменная-запись PL/SQL, которая целиком «укладывается» в таблицу в виде новой строки.
SQL> CREATE TABLE tab1 (at1 INTEGER, at2 VARCHAR2(1));
Table created.
SQL> DECLARE
2 l_tab1 tab1%ROWTYPE;
3 BEGIN
4 l_tab1.at1 := 1;
5 l_tab1.at2 := 'a';
6 INSERT INTO tab1 VALUES l_tab1;
7 l_tab1.at1 := 2;
8 l_tab1.at2 := 'b';
9 INSERT INTO tab1 VALUES l_tab1;
10 l_tab1.at2 := 'c';
11 UPDATE tab1 SET ROW = l_tab1 WHERE at1=2;
12 END;
13 /
PL/SQL procedure successfully completed.
SQL> SELECT * FROM tab1;
AT1 AT2
– –
1 a
2 c
Рекомендуется используемые в DML-командах записи PL/SQL объявлять на основе схем таблиц с помощью атрибута %ROWTYPE. Если впоследствии схемы этих таблиц изменятся, то код PL/SQL останется работоспособным. Таким образом, использование в DML-командах одной записи PL/SQL вместо нескольких переменных скалярных типов приводит к тому, что код становится более компактным и повышается его надежность.
Формирование предложений SQL со связываемыми переменными
Ранее отмечалось, что достоинством языка PL/SQL является формирование компилятором предложений SQL со связываемыми переменными.
Напомним, что связываемой переменной (bind variable) называется метка (placeholder) для переменной в тексте предложения SQL. Перед выполнением предложения SQL происходит связывание переменных (binding variables) – для них задаются фактические значения.
Мы сейчас не будем вдаваться в подробности, скажем только, что использование в предложениях SQL связываемых переменных вместо жестко кодируемых литералов (hard-coded literals) является обязательным условием достижения высокой производительности сервера Oracle.
Выполним программу PL/SQL с тремя командами добавления строк в таблицу test_tab и посмотрим, какие SQL-предложения INSERT были сформированы компилятором PL/SQL для байт-кода и потом выполнены в базе данных виртуальной машиной PL/SQL на самом деле:
CREATE TABLE test_tab (a INTEGER)
SQL> DECLARE
2 l_value INTEGER;
3 BEGIN
4 INSERT INTO test_tab VALUES(123);
5 l_value := 124;
6 INSERT INTO test_tab VALUES(l_value);
7 l_value := 125;
8 INSERT INTO test_tab VALUES(l_value);
9 END;
10 /
PL/SQL procedure successfully completed.
SQL> SELECT SUBSTR(sql_text,1,70) AS SQL_TEXT FROM V$SQL
2 WHERE LOWER(sql_text) LIKE LOWER('%test_tab%');
SQL_TEXT
–
INSERT INTO TEST_TAB VALUES(123)
INSERT INTO TEST_TAB VALUES(:B1 )
Видно, что для двух команд INSERT с переменной l_value компилятором PL/SQL сформировано одно предложение SQL со связываемой переменной :B1, потом оно было дважды выполнено с разными привязанными значениями :B1 (124 и 125). Для жестко закодированного (hard coded) литерала 123 замена на связываемую переменную компилятором PL/SQL не производилась.
Управление транзакциями в PL/SQL
Транзакции в базах данных Oracle
Транзакцией в базе данных Oracle называется атомарная (неделимая) логическая единица (unit) работы с базой данных, состоящая из одного или нескольких предложений языка SQL. Все транзакции в базах данных Oracle обладают четырьмя основными свойствами транзакций в базах данных:
атомарность (atomcity) – свойство транзакции, заключающееся в том, что она не может быть выполнена частично: транзакция либо успешно завершается с сохранением всех изменений в данных, либо все без исключения внесенные ею изменения данных полностью отменяются и измененные данные возвращаются к тому состоянию, в котором они находились перед началом транзакции;
согласованность (consistency) – свойство транзакции, заключающееся в том, что транзакция переводит базу данных из одного согласованного состояния в другое согласованное состояние;
изолированность (isolation) – свойство транзакции, заключающееся в ограничении влияния на ее работу других транзакций;
сохраняемость (durability) – свойство транзакции, обеспечивающее сохранность сделанных ею изменений данных после фиксации транзакции, независимо от программных сбоев и отказов оборудования.
Транзакции, обладающие всеми этими свойствами, называются ACID-транзакциями (по первым буквам английских названий свойств). Атомарность (неделимость) – главное свойство транзакции, наличие которого требуется ее определением. Все без исключения изменения транзакции или должны сохраняться в базе данных или же должны полностью отменяться.
Транзакция в Oracle начинается с первого модифицирующего данные предложения SQL (INSERT, UPDATE, DELETE), выполненного после окончания предыдущей транзакции, то есть предложения SELECT транзакцию не начинают. После своего начала транзакция может находиться в одном из трех состояний:
активная транзакция (active transaction) – транзакция, которая начата, но и не зафиксирована и для нее не выполнена отмена;
зафиксированная транзакция (committed transaction) – транзакция, для которой все выполненные ею изменения в данных являются постоянными (permanent);
отмененная транзакция (rolled back transaction) – транзакция, для которой все выполненные ею изменения в данных отменены (все измененные данные возвращены в исходное состояние, в котором они находились перед началом транзакции).
Активная транзакция в каждый момент времени характеризуется степенью своей активности:
выполняющаяся транзакция (running transaction) – транзакция, в рамках которой в этот момент времени выполняется одно из ее предложений SQL;
простаивающая транзакция (idle transaction).
Простаивающую активную транзакцию, все предложения SQL которой выполнены, но фиксации или отмены пока еще не было, называют выполненной. Фиксацию транзакции осуществляет SQL-команда COMMIT, отмену – SQL-команда ROLLBACK. Зафиксированная или отмененная транзакция называется завершенной (completed). После того, как транзакция завершилась, в этой пользовательской сессии следующее модифицирующее данные предложение SQL начинает следующую транзакцию.
В Oracle минимальной единицей данных, которая может быть изменена транзакцией, является строка. Получается, что в каждый момент времени любая строка любой таблицы базы данных может находиться в одном из двух состояний: