пятница, 13 декабря 2013 г.

Пользовательская блокировка в PLSQL Oracle (DBMS_LOCK)

Проблема

Предположим у нас есть PLSQL-функция записи в файл на диске. И есть функция удаления файла (например, чтобы начать записывать в новый). Значит во время удаления, ни одна другая сессия не должна писать в файл.

"Старое" решение

Можно сделать LOCK-таблицу с одним полем-PK, и при входе в функцию делать туда insert c константным ключём. Если вставка удачна, то выполняем код в самой функции и в конце строку из LOCK-таблицы. Любая другая сессия в это время при попытке вставки получит exception, который будет означать, что функция "заблокирована"

"Новое" решение

С некоторых пор (точно не знаю когда, но в 10-ке уже было), в БД Oracle появился пакет DBMS_LOCK, который предоставляет возможность блокировок в обычном программном ключе. Там есть возможности ожидания, shared и exclusive блокировок, ну и всё что в такого рода механизме должно быть.
Для нашего примера всё достаточно просто:

function turnFile return integer is
    lockId             varchar2(30);
    lockCode           number;
begin
    --выделена блокировка
    DBMS_LOCK.allocate_unique('MY_LOCK_NAME', lockId);
    --  запрошена блокировка в исключительном режиме (0)
    lockCode := DBMS_LOCK.request(lockId, dbms_lock.x_mode, 0);
    -- если захватить не удалось, то выходим, очищая попытку блокировки
    if lockCode <> 0 then
      lockCode := DBMS_LOCK.release(lockId);
      return;
    end if;
    
    -- что-то сделаем с файлом
    ...
    
    -- снять блокировку 
    lockCode := DBMS_LOCK.release(lockId);
exception
    when others then
      -- снять блокировку, если что-то пошло не так
      lockCode := DBMS_LOCK.release(lockId);    
end turnFile;

четверг, 12 декабря 2013 г.

Как правильно сложить/вычесть TIMESTAMP в PLSQL в Oracle


Как было с типом DATE

Когда надо было прибавить/отнять какой-то промежуток времени к переменной типа date, то с давних времён я пользовался обычным математическим преобразованием, исходя из того, что "1" - это 1 сутки = 24 часам = ...
 Например:

 declare
   vDate date default sysdate;
 begin
   -- прибавить vIntervalSec секунд
   vDate := vDate + 1/(24*60*60);
 end;

Что случилось с timestamp

Когда пришло время выполнить такую же операцию с переменной типа timestamp, то я поступил так же, и внешне всё сработало успешно...НО.... проблема обнаружилась, когда понадобилось поработать с миллисекундами. Выяснилось, что при таком типе преобразований, тип timestamp усекается до типа date

 declare
    vTS timestamp default = systimestamp;
  begin
    -- НЕЛЬЗЯ ТАК ДЕЛАТЬ !!!
    -- секунды прибавляются, НО миллисекунды стали =0 !!!
    vTS := vTS + 1/(24*60*60);
  end;

Решение

Для операций сложения/вычитания timestamp в PLSQL в Oracle надо использовать тип INTERVAL. И функции, которые работают с ним,например NUMTODSINTERVAL :

 declare
    vTS timestamp default systimestamp;
    vIntervalSec integer default 30;
  begin
    -- ПРАВИЛЬНО
    -- прибавим 30 секунд
    vTS := vTS + NUMTODSINTERVAL(v_attemptInterval, 'SECOND');
 end;