Дек. 212009
 

(към част 4)

С предишната част от поредицата показах един малко използван и доста мощен вариант за защита. В повечет случаи той е „прекалено“ ограничаващ (харесвам термина scalability inhibitor). Но може да се окаже полезен в случаи, когато едни данни се модифицират от токова много и различни места кода, че никой не ги знае всичките (спагети).

Обратно, когато се знае точно кои парчета от кода ще достъпват данните, се дефинират критични секции. За целта може да използва много интелигентно заключване с помощта на пакета dbms_lock. Този пакет предоставя възможност да се използват познатите ни вградени в Оracle механизми за заключване, за да управляваме достъпа до „наши“ си ресурси. Основно предимство пред hand-made заключваниците е, че механизма е доказан, тестван, железен и познат. За да може потребителя да използва този механизъм, трябва да има права за изпълнение на dbms_lock.

Ето малко примерен код:

create or replace procedure Process_New_Data is
  lck_handle number;
  lck_result binary_integer;
begin
  -- preventing multiple sessions run the same code
  DBMS_LOCK.ALLOCATE_UNIQUE(lockname => 'New_data_processing', lockhandle => lck_handle);
  lck_result := DBMS_LOCK.REQUEST(lockhandle => lck_handle, lockmode => dbms_lock.X_MODE, timeout => 0,
                                  release_on_commit => false);
  /* possible results:
    0 - Success; 1 - Timeout; 2 - Deadlock; 3 - Parameter error; 
    4 - Already own lock specified by id or lockhandle; 5 - Illegal lock handle
  */
  if lck_result != 0 then
    RAISE_APPLICATION_ERROR(-20115, 'Cannot allocate lock New_data_processing: ' || lck_result);
  end if;

  -- do wathever we want
  loop 
    insert . . .;
    delete . . .;
    commit; -- we can do many transactions, the lock is defined with release_on_commit => false
  end loop;
  update . . .;
  commit;

  -- release the lock
  lck_result := DBMS_LOCK.release(lockhandle => lck_handle);

EXCEPTION
  WHEN OTHERS THEN
    -- we should always release the lock!!!
    lck_result := DBMS_LOCK.release(lockhandle => lck_handle);
    -- do whatever error handling we need
    rollback;
    insert into Processing_log
      (time, module, action, error_msg, extra_data, status)
    values
      (sysdate, 'Process_New_Data', null,
       DBMS_UTILITY.FORMAT_ERROR_STACK() || chr(13) ||
       DBMS_UTILITY.FORMAT_ERROR_BACKTRACE(),
       null, 'Error');
    commit;
    raise;
end;

Основните моменти тук са:

– В началото си дефинираме „ресурс“, който ще заключваме, с помощта на DBMS_LOCK.ALLOCATE_UNIQUE

– После правим опит за заключване на този ресурс с DBMS_LOCK.REQUEST. Тук могат да се използват различни нива на закючване (Shared, Exclusive, SubShared, SubExclusive, Shared SubExclusive).
Може да изчакаме известно време (timeout) – това е много полезно, ако се очаква ресурса да бъде заключван за кратко, след което ние можем да успеем да го получим.
Има и още един много приятен параметър: release_on_commit. С него управляваме дали заключването да се освобождава автоматично при приключване на транзакцията (както правят всички нормални заключваници).
Една важна подробност е, че при неуспех процедурата не връща exception. За това трябва да се обработи резултата, който връща.

– След това може да се прави всичко според бизнес изискванията. Ако заключването е направено с release_on_commit => false, спокойно може да се реализира и код с много транзакции (като в част 1).

– На края ресурса се отключва с DBMS_LOCK.RELEASE. Тук отново при неуспех няма exception, а различен return value.

– Важно е да се подсигури „отключването“ на ресурса с exception handler. Ако по някаква причина не го направим и процедурата „гръмне“ по средата, той ще си остане заключен до края на сесията.

 Posted by at 13:27

  One Response to “Не сме сами, част 5”

  1. Благодаря. Много ми помогна тази информация.
    Едно уточнение да вмъкна. Резултат 4 от DBMS_LOCK.REQUEST всъщност е „Don’t own lock specified by id or lockhandle“.

Sorry, the comment form is closed at this time.