Рассмотрим следующий оператор:
Insert into t values(1);
Достаточно ясно, что он должен завершиться неудачей при нарушении ограничений — строка не будет вставлена. Однако рассмотрим следующий пример, где INSERT или DELETE по таблице T возбуждает триггер, который соответственно изменяет значение столбца CNT в таблице T2:
ops$tkyte@ORA10G> create table t2 ( cnt int );Table created.
Таблица создана.
ops$tkyte@ORA10G> insert into t2 values ( 0 );
1 row created.
1 строка создана.
ops$tkyte@ORA10G> commit;
Commit complete.
Фиксация завершена.
ops$tkyte@ORA10G> create table t ( x int check ( x>0 ) );
Table created.
Таблица создана.
ops$tkyte@ORA10G> create trigger t_trigger
2 before insert or delete on t for each row
3 begin
4 if ( inserting ) then
5 update t2 set cnt = cnt +1;
6 else
7 update t2 set cnt = cnt -1;
8 end if;
9 dbms_output.put_line( 'Я вставил и обновил ' ||
10 sql%rowcount || ' строк(у)' );
11 end;
12 /
Trigger created.
Триггер создан.
В этой ситуации уже не так ясно, что должно случиться. Если ошибка случится после срабатывания триггера, должен ли эффект от триггера оставаться в силе или нет? То есть, если триггер активизирован и обновил T2, но строка не была вставлена в T, каким должен быть результат? Ясно, что мы не хотим, чтобы в этом случае значение столбца CNT в T2 было увеличено, если строка не была действительно вставлена в T. К счастью, в Oracle исходный оператор, введенный клиентом — в этом случае INSERT INTO T— либо полностью проходит, либо полностью не проходит. То есть он является атомарным. Это можно подтвердить следующим образом:
ops$tkyte@ORA10G> set serveroutput on
ops$tkyte@ORA10G> insert into t values (1);
I fired and updated 1 rows
1 row created.
1 строка создана.
ops$tkyte@ORA10G> insert into t values(-1);
Я вставил и обновил 1 строк(у)
insert into t values(-1)
*
ERROR at line 1:
ORA-02290: check constraint (OPS$TKYTE.SYS_C009597) violated
ОШИБКА в строке 1:
ORA-02290: нарушение проверочного ограничения целостности (OPS$TKYTE.SYS_C009597)
ops$tkyte@ORA10G> select * from t2; CNT
1
На заметку! При использовании SQL*Plus из Oracle9i Release 2 и предшествующих версий для того, чтобы увидеть, что триггер сработал, вам нужно добавить строку кода exec null после второй вставки. Это связано с тем, что SQL*Plus в этой версии не извлекает и не отображает информацию DBMS_OUTPUT после неудачного оператора DML, тогда как в Oracle 10g — отображает.
Итак, одна строка была успешно вставлена в T, и мы получили надлежащее сообщение — “Я вставил и обновил 1 строк(у)”. Следующий оператор INSERT нарушает ограничение целостности, установленное для T. Сообщение DBMS_OUTPUT свидетельствует, что триггер T фактически сработал, и мы видим тому подтверждение. Он успешно выполнил свои обновления T2. Возможно, вы ожидали, что T2 теперь содержит значение 2, но на самом деле мы видим, что оно равно 1. СУБД Oracle делает исходный оператор INSERT атомарным — то есть первоначальный INSERT INTO T является оператором, а любой сторонний эффект этого оператора рассматривается как его часть.
СУБД Oracle достигает такой атомарности уровня оператора за счет того, что молча снабжает конструкцией SAVEPOINT каждый вызов в базе данных. Преды дущие два INSERT на самом деле трактуются так:
Savepoint statement1;
Insert into t values ( 1 );
If error then rollback to statement1;
Savepoint statement2;
Insert into t values ( -1 );
If error then rollback to statement2;
Для программистов, работавших с Sybase или SQL Server, поначалу это может показаться непонятным. В этих СУБД как раз истинно в точности обратное. В этих системах триггеры выполняются независимо от вызвавшего их оператора. Если возникает ошибка, триггеры должны явно откатить свою собственную работу и затем возбудить другую ошибку, чтобы откатить исходный оператор, который их инициирует. В противном случае работа, выполненная триггером, может остаться в силе, даже если вызвавший их оператор или другая его часть, в конечном счете, завершилась сбоем.
В Oracle атомарность уровня оператора распространяется так глубоко, как это должно быть. Если в предыдущем примере INSERT INTO T возбудит триггер, обновляющий другую таблицу, а та таблица снабжена триггером, выполняющим удаление в третьей таблице (и так далее, и тому подобное), то либо вся работа будет выполнена успешно, либо никакая выполнена не будет. Вам не нужно ничего дополнительно кодировать, чтобы обеспечить это — просто так оно работает.
< Предыдущая | Следующая > |
---|