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, 26 października 2015

Jeden parametr sekwencji a wydajność przy wstawianiu

Sekwencja zapewnia unikalność identyfikatorów, ale nie ich ciągłość na zbiorze liczb całkowitych.  W zależności od liczby wstawianych rekordów warto buforować w pamięci kolejne wartości sekwencji, zwłaszcza przy masowym wstawianiu. Stwórzmy sekwencję
CREATE SEQUENCE SEQ_START START WITHCACHE 2;
oraz prostą tabelę, gdzie w polu ID będziemy wstawiać kolejne wartości sekwencji
CREATE TABLE TBL_TEST
(
 ID NUMBER NOT NULL,
 DESCRIPTION_1 VARCHAR2(40 CHAR) NOT NULL,
 DESCRIPTION_2 VARCHAR2(40 CHAR) NOT NULL,
 DESCRIPTION_3 VARCHAR2(40 CHAR) NOT NULL
);

Poniższy blok wstawia do pustej tabeli  sto tysięcy rekordów bez korzystania z sekwencji. Uśredniony czas wykonania to 4,3 sekundy..
DECLARE
    vn_Start NUMBER;
BEGIN
  EXECUTE IMMEDIATE
  'TRUNCATE TABLE TBL_TEST'
;
  vn_Start := dbms_utility.get_time;
  
  FOR EL IN  1..100000
   LOOP
       INSERT INTO ALTAIRDB.TBL_TEST (
        ID, DESCRIPTION_1, DESCRIPTION_2,
        DESCRIPTION_3)
    VALUES (  EL /* ID */,
        'DESCRPTION_1 ' || EL /* DESCRIPTION_1 */,
        'DESCRPTION_2 ' || EL /* DESCRIPTION_2 */,
        'DESCRPTION_3 ' || EL /* DESCRIPTION_3 */ );
   END LOOP;
   COMMIT;
dbms_output.put_line( ROUND( (dbms_utility.get_time- vn_start)/100, 2 ) || ' sekund...' );
END;

W poniższym przykładzie wstawiamy wielokrotnie do pustej tabeli po  sto tysięcy rekordów z wykorzystaniem sekwencji z różnym parametrem buforowania.
DECLARE.
vn_Start NUMBER;
BEGIN
  FOR
EL_OUT IN (SELECT COLUMN_VALUE AS CACHE_SIZE,
                    DECODE( COLUMN_VALUE, 1 , 'NOCACHE', 'CACHE ' || COLUMN_VALUE)   AS CACHE_DDL
                 FROM TABLE(SYS.ODCINUMBERLIST(1, 2,5,10, 20, 50, 100, 500, 1000, 5000, 10000))) 
  LOOP
    BEGIN
        EXECUTE IMMEDIATE
'DROP SEQUENCE SEQ_START'
;
    EXCEPTION WHEN OTHERS THEN
        NULL
;   
    END;
  EXECUTE IMMEDIATE 'TRUNCATE TABLE TBL_TEST';
  EXECUTE IMMEDIATE 'CREATE SEQUENCE SEQ_START START WITH 1 ' || EL_OUT.CACHE_DDL;
  vn_Start := dbms_utility.get_time;
    FOR EL_IN IN  1..100000
       LOOP
           INSERT INTO
TBL_TEST (
            ID, DESCRIPTION_1, DESCRIPTION_2,
            DESCRIPTION_3)
        VALUES (  SEQ_START.nextval /* ID */,
            'DESCRPTION_1 ' || EL_IN /* DESCRIPTION_1 */,
            'DESCRPTION_2 ' || EL_IN /* DESCRIPTION_2 */,
            'DESCRPTION_3 ' || EL_IN /* DESCRIPTION_3 */ );
       END LOOP;
    COMMIT;
  dbms_output.put_line( EL_OUT.CACHE_DDL || ' - ' || ROUND( (dbms_utility.get_time- vn_start)/100, 2 ) || ' sekund...' );
  END LOOP;
END;

Wyniki:
NOCACHE - 26,9 sekund...
CACHE 2 - 17,82 sekund...
CACHE 5 - 10,82 sekund...
CACHE 10 - 8,78 sekund...
CACHE 20 - 7,79 sekund...
CACHE 50 - 7,17 sekund...
CACHE 100 - 7,01 sekund...
CACHE 500 - 6,94 sekund...
CACHE 1000 - 6,95 sekund...
CACHE 5000 - 6,87 sekund...
CACHE 10000 - 6,56 sekund...

To jest przykład na to, jak mało ważne  parametry mogą istotnie zmniejszyć wydajność,zwłaszcza dla masowego przetwarzania...

poniedziałek, 12 października 2015

Algorytmy obliczeniowe - walka wydajności z przewidywalnością

Napiszmy krótki kawałek kodu, który wykonuje niezbyt sensowne obliczenia korzystając z wbudowanych funkcji matematycznych. Kod zostanie wykonany na serwerze Exadata

DECLARE
vn_Pom  NUMBER :=0;
vn_Start NUMBER;
BEGIN
  vn_Start := dbms_utility.get_time;
  vn_Pom := 1.2;  
  FOR EL IN  1..100000
   LOOP
        vn_Pom := COS(SIN(vn_Pom));
        vn_Pom :=1+ vn_Pom + POWER(2, vn_Pom);
        vn_Pom  := LOG(2, ABS(vn_Pom) + 3);  
   END LOOP;
dbms_output.put_line( ROUND( (dbms_utility.get_time- vn_start)/100, 2 ) || ' sekund...' ); 
END;

Średni  czas wykonania dla trzech  przebiegów to 9,02 sekundy.

Zmieńmy teraz jedną podkreśloną linię w powyższym anonimowym bloku kodu.
vn_Pom  BINARY_DOUBLE :=0;

Średni czas wykonania zmniejszył się do 0,06 sekundy.

Skąd wynika ta zmiana??

W pierwszym przypadku typ NUMBER jest uniwersalny i niezależny od maszyny. Jest obsługiwany za pomocą dołączonym bibliotek napisanych w C i na każdym serwerze dla jednakowych operacji  powinny być zwracane jednakowe wartości. Bardzo czasochłonne są funkcje matematyczne wykorzystujące prawdopodobnie rozwinięcie w szereg funkcji trygonometrycznych, co jest dość zasobożerne. Ale zawsze otrzymamy ten sam wynik !!! Jest to cecha niezwykle istotna w przypadku rozproszonych systemów transakcyjnych korzystających z niejednorodnej infrastruktury sprzętowej. Jest to emulacja niezależna od typu serwera.

Natomiast w drugim przypadku deklaracja zmiennej vn_Pom  BINARY_DOUBLE :=0 informuje  serwer, że obliczenia będą przeprowadzone  przy wykorzystaniu koprocesora matematycznego w obrębie procesora serwera. To odpowiednik typu double w C przy wykorzystaniu koprocesora. Jet to rozwiązanie bardzo szybkie, tym niemniej zachodzi uzasadniona obawa, ze dla rożnych platform sprzętowych, wyniki obliczeń będą się różnić ostatnimi cyframi.  Należy tez zauważyć, że koprocesory nie obsługują wartości NULL, która to jest wysokopoziomowym wynalazkiem, stąd przymusowa inicjalizacja zmiennej. Ma to też swoją zaletę - taki kod na etapie testowania zawsze będzie generować wyjątki, kiedy napotka wartość NULL i ułatwi wykrycie miejsc, skąd przechodzą wartości niezainicjalizowane..