4 августа 2011 г.

oracle: Транзакции

Transactions

Выдержки из Тома Кайта.

Транзакции в Oracle удовлетворяют всем требуемым характеристикам  ACID.
Аббревиатура ACID   означает:
•   атомарность (atomicity) — выполняется либо вся транзакция целиком, либо она целиком не выполняется;
•   согласованность (consistency) — транзакция переводит базу данных из одного согласованного состояния в другое;
•   изоляция (isolation) — эффект от транзакции не виден другим транзакциям до тех пор, пока она не будет зафиксирована;
•   устойчивость (durability) — как только транзакция зафиксирована, она остается постоянной.



Атомарность

Рассмотрим пример:
Создадим таблицы T(вставляем сюда строки) и T2(содержит счётчик строк в таблице T), и триггер, который при вставке строки в T, увеличивает значение счётчика в Т2, при удалении - уменьшает.



-- Т
CREATE TABLE t ( x INT CHECK ( x>0 ) );

-- Т2
CREATE TABLE t2 (cnt INT);

-- триггер
CREATE TRIGGER t_trigger
   BEFORE INSERT OR DELETE
   ON guppi.t
   FOR EACH ROW
BEGIN
   IF (INSERTING)
   THEN
      UPDATE t2
         SET cnt = cnt + 1;
   ELSE
      UPDATE t2
         SET cnt = cnt - 1;
   END IF;

   DBMS_OUTPUT.put_line ('Я вставил и обновил ' || SQL%ROWCOUNT || ' строк(у)');
END;


-- вставим в Т2 назчальное значение счётчика cnt=0, если таблица Т2 будет пустая, то триггер не -- сможет работать, ему просто нечего будет обновлять
insert into t2 values(0);

-- вставим в Т строку
insert into t values(1);

Я вставил и обновил 1 строку

В Т2 cnt = 1.

-- вставим в Т строку нарушающую ограничение (x INT CHECK ( x>0 ))
insert into t values(-1);
Я вставил и обновил 1 строк(у)
insert into t values(-1)
ORA-02290: check constraint (GUPPI.SYS_C0079627) violated


Сообщение "Я вставил и обновил 1 строк(у)" показывает, что триггер сработал, но мы нарушили ограничение целостности ORA-02290 строка в Т вставлена не была:
SQL> select * from t;

                                      X
---------------------------
                                      1
Какое же тогда значение счётчика в Т2, ведь триггер сработал?
SQL> select * from t2;

                                    CNT
-----------------------------
                                       1
Ожидалось увидеть cnt=2, но нет, cnt=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;

В Oracle атомарность уровня оператора распространяется так глубоко, как это должно быть. Если в предыдущем примере INSERT  INTO  T возбудит триггер, обновляющий другую таблицу, а та таблица снабжена триггером, выполняющим удаление в третьей таблице (и так далее, и тому подобное), то либо вся работа будет выполнена успешно, либо никакая выполнена не будет. Вам не нужно ничего дополнительно кодировать, чтобы обеспечить это — просто так оно работает.


Атомарность уровня процедуры

Используем те же таблицы Т и Т2, вернёмся к начальным условиям - удалим все строки из Т и установим cnt=0 в Т2.
Создадим процедуру, с 2мя инсертами в ней, 1й правильный, а второй нарушает ограничение целостности.

create or replace procedure p as
begin
  insert into t values (1);
  insert into t values (-1);
end;

Запустим процедуру:
SQL> exec p;

Я вставил и обновил 1 строк(у)
Я вставил и обновил 1 строк(у)


begin p; end;


ORA-02290: check constraint (GUPPI.SYS_C0079627) violated
ORA-06512: at "GUPPI.P", line 4
ORA-06512: at line 2

Проверяем, что в таблицах Т и Т2:
SQL> select * from t;

                                      X
---------------------------------------

SQL> select * from t2;

                                    CNT
---------------------------------------
                                      0

Oracle трактует вызов хранимой процедуры как атомарный оператор. Клиент посылает блок кода BEGIN P; END;, а Oracle обертывает его в SAVEPOINT. Поскольку процедура P терпит неудачу, Oracle восстанавливает базу данных в состояние, которое она имела в точке, непосредственно предшествовавшей ее вызову.

 Изменим блок вызова процедуры:
SQL> begin p;
  2  exception
  3  when others then null;
  4  end;
  5  /

Я вставил и обновил 1 строк(у)
Я вставил и обновил 1 строк(у)

PL/SQL procedure successfully completed

Проверяем, что в таблицах Т и Т2:
SQL> select * from t;

                                      X
---------------------------------------
                                      1

SQL> select * from t2;

                                    CNT
---------------------------------------
                                      1

Мы добавили к вызову процедуры блок кода, чтобы все возможные ошибки игнорировались. Процедура завершается успехом за счёт самостоятельного перехвата и игнорирования ошибок.
Поэтому частичная работа процедуры p сохраняется - 1й инсерт проходит, 2й неправильный нет.

Том Кайт считает ошибочным практически любой код, который содержит обработчик исключений WHEN  OTHERS, но не включает вызова RAISE, чтобы передать исключение выше.