Blog programisty w Oracle PL/SQL

Jest to blog eksperymentatora programisty w PL/SQL dla Oracle. Wszystkie kody tutaj zamieszczone mogą być dowolnie wykorzystywane i zmieniane. A jeśli Ktoś z Gości znajdzie błąd, będę niezwykle wdzięczny...
Zapisz

Szukaj na tym blogu

poniedziałek, 27 grudnia 2010

Zabawa klockami - przyśpieszamy budowę wieży Hanoi

Wieża  Hanoi jest  algorytmem bardzo krótkim i zwięzłym o wykładniczo rosnącej złożoności, wraz z liczbą krążków.
Implementacja w języku PL/SQL jest bardzo prosta..

CREATE OR REPLACE PROCEDURE Pdb_HanoiTower
( vn_Level NUMBER, vn_From NUMBER, vn_To NUMBER,
vn_Helper NUMBER) IS
 BEGIN
  IF vn_Level > 0 THEN
   Pdb_HanoiTower( vn_Level-1, vn_From, vn_Helper, vn_To);
    Pdb_HanoiTower(vn_Level-1, vn_Helper, vn_To, vn_From);
  END IF;
  END;
Do  testowania wydajności procedury wykorzystajmy poniższy blok:
DECLARE
 vn_Start NUMBER;
BEGIN
 vn_Start := dbms_utility.get_time;
 PdB_HanoiTower(25,1,3,2);
 DBMS_OUTPUT.PUT_LINE( round( (dbms_utility.get_time- vn_start)/100, 2 ) || ' seconds...' );
END;
Blok wykonał się w 16,38 s (średnia z kilku pomiarów).
Spróbujmy przyśpieszyć… Zauważmy, że do przekazywania parametrów wykorzystywany jest typ number, który zapewnia bardzo duży zmiennoprzecinkowy zakres liczbowy, a w naszym przypadku wystarczy zakres  typu SIMPLE_INTEGER – jest to odpowiednik czterobajtowego typu signed  int  z języka C bez sprawdzenia przepełniania  ( dla zwiększenia wydajności).. Unikniemy także niepotrzebnych konwersji między typami numerycznymi zmiennopozycyjnymi i całkowitymi..
CREATE OR REPLACE PROCEDURE Pdb_HanoiTower
( vn_Level SIMPLE_INTEGER, vn_From SIMPLE_INTEGER, vn_To SIMPLE_INTEGER,
vn_Helper SIMPLE_INTEGER) IS
 BEGIN
  IF vn_Level > 0 THEN
    Pdb_HanoiTower( vn_Level-1, vn_From, vn_Helper, vn_To);
    Pdb_HanoiTower(vn_Level-1, vn_Helper, vn_To, vn_From);
  END IF;
  END;
Blok testowy dla procedury w powyższej postaci wykonał się w 15.08 s (czas uśredniony).
Przyśpieszajmy dalej..  Ponieważ w trakcie działania procedury wywoływane są  dwie procedury z czterema parametrami, zastosujmy mechanizm inline znany z języka C++ - zamiast wskaźnika do funkcji, wstawiane jest rozwinięcie funkcji..
CREATE OR REPLACE PROCEDURE Pdb_HanoiTower
( vn_Level SIMPLE_INTEGER, vn_From SIMPLE_INTEGER, vn_To SIMPLE_INTEGER,
vn_Helper SIMPLE_INTEGER) IS
 BEGIN
  IF vn_Level > 0 THEN
    PRAGMA INLINE (Pdb_HanoiTower, 'YES');
    Pdb_HanoiTower( vn_Level-1, vn_From, vn_Helper, vn_To);
    PRAGMA INLINE (Pdb_HanoiTower, 'YES');
    Pdb_HanoiTower(vn_Level-1, vn_Helper, vn_To, vn_From);
  END IF;
  END;
Blok testowy wykonał się w 8.66 s – to prawie dwukrotne przyśpieszenie..
Domyślnie funkcjonalności PL/SQL kompilowane są do kodu pośredniego wykonywanego na maszynie wirtualnej.. Oracle 11g oferuje kompilaję do kodu natywnego dla danego systemu operacyjnego.. Wykorzystajmy to
ALTER PROCEDURE PdB_HanoiTower COMPILE PLSQL_CODE_TYPE = NATIVE
Czas wykonania zmniejszył się  do 3.83 s. – przyrost ponad czterokrotny..
Usuńmy teraz  wskazówki do rozwinięcia funkcji inline (dodawanie ich jest mocno uciążliwe), zachęćmy  kompilator do samodzielnego rozwinięcia i innych sztuczek optymalizacyjnych( parametr kompilacji PLSQL_OPTIMIZE_LEVEL = 3).
Kod wraca do czytelnej postaci:
CREATE OR REPLACE PROCEDURE Pdb_HanoiTower
( vn_Level SIMPLE_INTEGER, vn_From SIMPLE_INTEGER, vn_To SIMPLE_INTEGER,
vn_Helper SIMPLE_INTEGER) IS
 BEGIN
  IF vn_Level > 0 THEN
    Pdb_HanoiTower( vn_Level-1, vn_From, vn_Helper, vn_To);
    Pdb_HanoiTower(vn_Level-1, vn_Helper, vn_To, vn_From);
  END IF;
  END;
Po kompilacji
ALTER PROCEDURE PdB_HanoiTower COMPILE PLSQL_CODE_TYPE = NATIVE  PLSQL_OPTIMIZE_LEVEL = 3
Czas  wykonania zmniejszył  się do 3.43..
Warto było?? Uważam, że tak!
Uwagi:
·         Powyższy przykład dotyczy Oracle 11g
·         Po sprawdzeniu na platformach  Windows 64 i HP Unix 64 otrzymałem dość zbliżone przyrosty, wstawiłem z pierwszego środowiska
·         Optymalizacja powyższa przeznaczona jest  raczej dla algorytmów obliczeniowych lub operujących ma łańcuchach, w mniejszym stopniu opierających się na częstych operacjach  bazodanowych

środa, 22 grudnia 2010

Zasięg zmiennych i bloki anonimowe w procedurach

Przykład wszystko tłumaczy...
 
CREATE OR REPLACE PROCEDURE PDB_Test IS
   vn_I  PLS_INTEGER := 0;
BEGIN
   vn_I := 1;
   <<JUMP_OUT>>
   DECLARE
      vn_I PLS_INTEGER;
   BEGIN
       vn_I := 2;
       <<JUMP_IN>>
       DECLARE
        vn_I PLS_INTEGER;
       BEGIN
         vn_I := 3;
        dbms_output.put_line('vn_I=' || vn_I);
        dbms_output.put_line('JUMP_OUT.vn_I=' || JUMP_OUT.vn_I);
        dbms_output.put_line('JUMP_OUT.JUMP_IN.vn_I=' ||    JUMP_OUT.JUMP_IN.vn_I);
        dbms_output.put_line('PDB_Test.vn_I=' || PDB_Test.vn_I);
      END JUMP_IN;
   END JUMP_OUT;
END ;

wtorek, 7 grudnia 2010

Trigger dla leniwych programistów PL/SQL

  Tworząc pakiety, procedury, funkcje, wyzwalacze, modyfikując strukturę bazy danych nie zawsze mamy chęć i cierpliwość do dokumentowania naszych zmian... Nierzadko pojawia się problem - ostatnie poprawki więcej napsuły niż poprawiły, dodaliśmy do pewnych tabel nowe pola, ale jakie a do obiektów bazodanowych nowe uprawnienia... Najlepiej byłoby takie zmiany śledzić automatycznie bez rozpraszania programisty - poniżej przedstawiam wizję pewnego rozwiązania opartego na pewnym przykładzie dołączanym z dokumentacją Oracle....


Instalację elementów  należy dokonać w osobnym schemacie niedostępnym dla programistów... Trigger przekażmy do instalacji  DBA...
   Stwórzmy następujące elementy bazodanowe - świadomie nie podaję nazw schematów:
  • sekwencję do indeksowania tabeli historii zmian
  CREATE SEQUENCE SEQ_DDL_CHANGE
     START WITH 0
     MAXVALUE 9999999999999
     MINVALUE 0
     NOCYCLE
     NOCACHE
     NOORDER;
  •  tabelę zawierającą zmiany
CREATE TABLE TBL_DDL_LOG
(
  ID              NUMBER(10)                    NOT NULL,
  SYSEVENT        VARCHAR2(50 CHAR)             NOT NULL,
  DICT_OBJ_OWNER  VARCHAR2(30 CHAR)             NOT NULL,
  DICT_OBJ_NAME   VARCHAR2(30 CHAR),
  DDL_STATEMENT   CLOB,
  USER_NAME       VARCHAR2(30 CHAR)  DEFAULT USER         NOT NULL,
  SYSTEM_DATE     DATE               DEFAULT SYSDATE      NOT NULL
);

 CREATE UNIQUE INDEX PK_DDL_LOG ON TBL_DDL_LOG(ID);
 ALTER TABLE TBL_DDL_LOG ADD (CONSTRAINT PK_DDL_LOG PRIMARY KEY(ID));
  • tabele z listą użytkowników, którzy są śledzić swoje zmiany. Nie umieszczajmy w jej rekordach osób mających perfekcjonizm  we krwi
 
CREATE TABLE TBL_DDL_USER
(
  USER_LOGIN   VARCHAR2(30 CHAR)    NOT NULL,
  USER_NAME    VARCHAR2(30 CHAR)    DEFAULT USER         NOT NULL,
  SYSTEM_DATE  DATE    DEFAULT SYSDATE     NOT NULL
)
  •   oraz sam kod triggera - pole typu LOB zmieści wszelkie  bardzo rozbudowane programistyczne wizje. Trigger należy instalować jako użytkownik sys. Należy pamiętać o dodaniu nazw schematów do wstawianych tabel
CREATE OR REPLACE TRIGGER TRG_TEST
   AFTER DDL OR GRANT OR REVOKE
   ON DATABASE
DECLARE
   vc_SqlText   ora_name_list_t;
   vc_Stmt      CLOB;
   vn_Size         PLS_INTEGER;
BEGIN
   SELECT   COUNT ( * )
     INTO   vn_Size
     FROM   TBL_DDL_USER
    WHERE   USER_LOGIN = USER;

   IF vn_Size > 0
   THEN
      vn_Size := ora_sql_txt (vc_SqlText);

      FOR i IN 1 .. vn_Size
      LOOP
         vc_Stmt := vc_Stmt || vc_SqlText (i);
      END LOOP;

      INSERT INTO TBL_ddl_log (ID,
                                   SYSEVENT,
                                   DICT_OBJ_OWNER,
                                   DICT_OBJ_NAME,
                                   DDL_STATEMENT,
                                   USER_NAME,
                                   SYSTEM_DATE)
        VALUES   (SEQ_DDL_CHANGE.NEXTVAL,
                  ora_sysevent,
                  ora_dict_obj_owner,
                  ora_dict_obj_name,
                  vc_Stmt,
                  USER,
                  SYSDATE);
   END IF;
EXCEPTION
   WHEN OTHERS
   THEN
      RETURN;
END;
/


W prosty sposób kod z własnego schematu można pobrać za pomocą poniższego zapytania:

SELECT OBJECT_NAME, OBJECT_TYPE,DBMS_METADATA.GET_DDL( REPLACE(OBJECT_TYPE,' ', '_'), OBJECT_NAME)
FROM
(SELECT DISTINCT  OBJECT_NAME, OBJECT_TYPE FROM USER_PROCEDURES)