Warning: Declaration of Suffusion_MM_Walker::start_el(&$output, $item, $depth, $args) should be compatible with Walker_Nav_Menu::start_el(&$output, $item, $depth = 0, $args = Array, $id = 0) in /DISK2/WWW/plsql.cz/www/wp-content/themes/suffusion/library/suffusion-walkers.php on line 0
Jun 022013
 

Musím říct, že jsem několikrát ve svém životě potřeboval z DB vygenerovat nějaký report a pokaždé když došlo na potřebu grafické reprezentace, snažil jsem se tomu zuby nehty vyhnout. Jenže obrázky jsou sexy, ne?  Takže můj cíl je nyní generovat obrázky z databáze. Zatím pouze obrázky a pouze *.BMP. Mě by se totiž mnohem více hodila komprimovaná videa, nicméně je třebas dekomponovat. Ostatně generování  *.BMP bude mít jednu krásnou vlastnost – bude možné vidět případný sub-výsledek až bych třebas psal JPEG komprimaci v PL/SQL. Mnohem rychlejší by bylo napsat to v Javě a javu si provolat či jí přímo loadnout do Oracle – funkční, rychlé, efektivní – ale nudné.

Vím o jednom problému, který mi čeká a o kterém ještě nemám úplnou představu jak ho řešit – je třeba do obrázku mimo kreslení psát, což znamená mít představu jako takové písmeno (“A”) bude v bitmapě vypadat a zatímco v Javě to stačí drawStringnout na Canvas (či Canvas2d) tady to bude vyžadovat mnohem více. Což třebas povede na nějaké čtení fontů(?) a loadování do DB a o to to bude zajímavějšejší, inu, uvidíme.

Takže hurá do hébičkování !

Bitmapa (jako BMP) vypadá jako soubor pixelů a nic víc. Nicméně i tak má BMP soubor nějakou hlavičku, kterou je třebas zapsat: Při čtení článku na wiki jsem dokonce zjistil, že BMP umožňuje Huffamanovo kódování a tedy bezztrátovou komprimaci v jisté omezené podobě, což jsem do dneška netušil.

http://en.wikipedia.org/wiki/File:BMPfileFormat.png

Což znamená, že bílý jednopixelový (1×1) obrázek nejsou 3 bajty (24 bitů – 8×3 barvy), ale 58 bajtů:

Jeden bilý pixel v BMP

Pro pochopení jsem si vyzkoušel nějakou úpravu (modře) – konkrétně jsem z 01 přepsal pixely na 02 (2x 2x) a přidal 12 bajtů:

Čtyři pixely v BMP (uprava v hexaeditoru)

Fajn, to by zafungovalo, takže společně s obrázkem z wikipedie už je asi pomalu možné nějaké BMP začít tvořit.  Nejprve nastřelení nějakého minimálního kódu, že je to správný směr:

1) Založení adresáře, kam se budou cpát BMP soubory:
CREATE DIRECTORY output_bmp AS 'F:\bmp_output';

2) Úplně minimální kód na zkoušku:
declare
bmp_file utl_file.file_type;
un_used_1 RAW(4):=hextoraw('42')||hextoraw('4d');
begin
bmp_file:=utl_file.fopen('OUTPUT_BMP','test.bmp','wb');
utl_file.put_raw(bmp_file,un_used_1,true);
if utl_file.is_open(bmp_file) then
utl_file.fclose_all;
end if;
end;

Je to opravdu minimální kód, který do souboru test.bmp zapíše pouze 42 a 4D (hex), což odpovídá ascii textu ‘BM‘ a je to první část hlavičky bitmapového souboru. Což v hexaeditoru (použivám WinHex 17.0 a vřele doporučuji – umí přímý zápis na disk i do RAM), vypadá takto:

První část hlavičky BMP souboru

Fajn, to by bylo a nyní minimální kód, pro vygenerování nejjednoduššího BMP – hlavičku, jsem sprostě opráskl z wikipedie:

declare
/* uvodni BMP hlavička BMP */
static_magic_header RAW(2):=hextoraw('42')||hextoraw('4d');
/* velikost obrázku v B */
variable_image_size RAW(4):=hextoraw('46')||hextoraw('00')||hextoraw('00')||hextoraw('00');
/* aplication specific - takze se podepišu AZOR - AZ */
static_aplication_1 RAW(2):=hextoraw('41')||hextoraw('5A');
/* aplication specific - takze se podepišu AZOR - OR */
static_aplication_2 RAW(2):=hextoraw('4F')||hextoraw('52');
/* kde končí hlavička */
static_head_end RAW(4):=hextoraw('36')||hextoraw('00')||hextoraw('00')||hextoraw('00');
/* délka DIB hlavičky */
static_dib_size RAW(4):=hextoraw('28')||hextoraw('00')||hextoraw('00')||hextoraw('00');
/* sířka obrázku */
variable_image_width RAW(4):=hextoraw('02')||hextoraw('00')||hextoraw('00')||hextoraw('00');
/* výška obrázku */
variable_image_height RAW(4):=hextoraw('02')||hextoraw('00')||hextoraw('00')||hextoraw('00');
/* počet planes */
static_image_planes RAW(2):=hextoraw('01')||hextoraw('00');
/* počet barev - 24 bitová paleta */
static_image_bits RAW(2):=hextoraw('18')||hextoraw('00');
/* bi_rgb */
static_bi_rgb RAW(4):=hextoraw('00')||hextoraw('00')||hextoraw('00')||hextoraw('00');
/* velikost pixel array (dat) - a BEZ KOMPRSE*/
variable_raw_size RAW(4):=hextoraw('10')||hextoraw('00')||hextoraw('00')||hextoraw('00');
/* pixels/metters - horizontalne i vertikalne pouziju jednu promennou */
static_pix_metter RAW(4):=hextoraw('13')||hextoraw('0B')||hextoraw('00')||hextoraw('00');
/* nezbytné barvy - nulla */
static_colors RAW(4):=hextoraw('00')||hextoraw('00')||hextoraw('00')||hextoraw('00');
/* A KONEČNĚ JEDNOTLIVÉ PIXELY (prvně však separátor) */
static_pix_separator RAW(2):= hextoraw('00')||hextoraw('00');
data_pix_1_1 RAW(3):=hextoraw('00')||hextoraw('FF')||hextoraw('FF');
data_pix_1_2 RAW(3):=hextoraw('00')||hextoraw('00')||hextoraw('FF');
data_pix_2_1 RAW(3):=hextoraw('FF')||hextoraw('00')||hextoraw('FF');
data_pix_2_2 RAW(3):=hextoraw('FF')||hextoraw('00')||hextoraw('FF');
bmp_file utl_file.file_type;
begin
bmp_file:=utl_file.fopen('OUTPUT_BMP','test_2.bmp','wb');
/* postupné zapisování všech hlaviček */
utl_file.put_raw(bmp_file,static_magic_header,true);
utl_file.put_raw(bmp_file,variable_image_size,true);
utl_file.put_raw(bmp_file,static_aplication_1,true);
utl_file.put_raw(bmp_file,static_aplication_2,true);
utl_file.put_raw(bmp_file,static_head_end,true);
utl_file.put_raw(bmp_file,static_dib_size,true);
utl_file.put_raw(bmp_file,variable_image_width,true); -- šířka
utl_file.put_raw(bmp_file,variable_image_height,true); -- výška
utl_file.put_raw(bmp_file,static_image_planes,true);
utl_file.put_raw(bmp_file,static_image_bits,true);
utl_file.put_raw(bmp_file,static_bi_rgb,true);
utl_file.put_raw(bmp_file,variable_raw_size,true);
utl_file.put_raw(bmp_file,static_pix_metter,true);
utl_file.put_raw(bmp_file,static_pix_metter,true);
utl_file.put_raw(bmp_file,static_colors,true);
utl_file.put_raw(bmp_file,static_colors,true);
/* a konečně data */
utl_file.put_raw(bmp_file,data_pix_1_1,true);
utl_file.put_raw(bmp_file,data_pix_1_2,true);
/* separátor řádku */
utl_file.put_raw(bmp_file,static_pix_separator,true);
/* druhý řádek */
utl_file.put_raw(bmp_file,data_pix_2_1,true);
utl_file.put_raw(bmp_file,data_pix_2_2,true);
/* ukončení souboru */
utl_file.put_raw(bmp_file,static_pix_separator,true);
/* a zavřít soubor */
if utl_file.is_open(bmp_file) then
utl_file.fclose_all;
end if;
end;

A wohoho – 4pixelový výsledek uložený v souboru test_2.bmp vypadá po zvětšení takto :

Vygenerované BMP

Vygenerované BMP

Bohužel jsem změnil bezhlavě barvu a vypadla z toho růžová :/, zrovna taková hloupá barva. Kód rozhodně není hezký a v nějakém dalším díle ho upravím. Zatím jsem vytipoval části hlaviček, které nebudu měnit (prefix static_) a ty které se s velikostí obrázku budou muset měnit a přepočítávat (prefix variable_). Vygenerovaný obrázek vypadá pak v hexaeditoru:

První BMP v Oracle zobrazená v WinHex

První BMP v Oracle zobrazená v WinHex

Tak a to by bylo pro tento díl všechno ;) V druhém to navrhuji vykrášlit a udělat nějakou funkci, která mi z BLOBu vrátí přímo hlavičku. A taky funkci, která vygeneruje prázdný obrázek. Ještě s tím bude spousta legrace.

 Posted by at 21:36
Dec 022012
 

Jedna z věcí,  která mi trošku trápí a o které si bohužel myslím, že povede na regulerní výrazy, scripty a parsování predikátů exekučního plánu. Protože jinak nevím, jak takovouhle věc systémově podchytit – mimo outlines a podobných featur.

Jde mi o join na sloupci, který je jeden VARCHAR2 a druhý NUMBER (alternativně něco podobného), kde může správně zafungovat implicitní konverze (na NUMBER), pokud jsou ve VARCHAR2 sloupci jen čísla, která implicitní konverze zkousne – v takovém případně select doběhne. Pokud tam čísla nejsou a explicitně to nepřevedeme pomocí to_char, tak select vyhučí na ORA-01722. To zní fér, ne?

Bohužel do toho ještě může promluvit exekuční plán a pořadí joinování tabulek, což je opravdu nepěkná vlastnost – programátor si nevšimne, že joinuje NUMBER na VARCHAR2 a select mu dobíhá bez chyby. Pokud se testuje (mělo by) tak třeba i projde na produkci, kde funguje nějaký pátek a pak najednou změna exekučního plánu a průser.

Slov bylo dost, takže go, hébičky, go!

create table tbl_a as select rownum id_a,rownum join_column_a from dual connect by level <=100;

Jednoduchá tabulka se sloupci id_a a join_column_a ve kterých jsou čísla od 1 do 100, typ: NUMBER. Nyní ještě jedna, ta samá tabulka, ale přejmenovaná na surfix _c:

create table tbl_c as select rownum id_c,rownum join_column_c from dual connect by level <=100;

tbl_c je stejná jako tbl_a. Mnohem zajímavější bude tabulka tbl_b:

create table tbl_b as select rownum id_b,
case when rownum=23 then 'au' else to_char(rownum) end join_column_b from dual connect by level <=100;

v tbl_b je sloupec id_b typu NUMBER ve kterém jsou čísla 1-100 a také sloupec join_column_b typu VARCHAR2(40) ve kterém jsou čísla <1-22> a <24-100> a text “au”.

Tabulky jsou připraveny, takže jdeme joinovat:

select * from tbl_a, tbl_b where tbl_a.join_column_a=tbl_b.join_column_b;

Tak tbl_a.join_column_a je typu NUMBER, tbl_b.join_column_b je typu VARCHAR2 a obsahuje v jednom řádku text “au”. Takže implicitní konverze to vezme na NUMBER a select vyhučí na ORA-01722. Takže to_char, explicitní konverze:

select * from tbl_a, tbl_b where to_char(tbl_a.join_column_a)=tbl_b.join_column_b;

Ok, explicitně zkonvertováno na string a select normálně doběhne. O co mi jde, je ale tenhle select:

select * from tbl_a, tbl_b,tbl_c where
tbl_a.join_column_a=tbl_b.join_column_b and /* number vs varchar2 s textem v id=23 */
tbl_c.id_c=tbl_b.id_b and /* number vs number */
id_c >(select 24 from dual);

Vyhučí to na chybě nebo, ne? Na jednu stranu se tam joinuje zkrz implicitní koverzi VARCHAR2 s nečíselnou hodnotou na NUMBER na stranu druhou je tam ale filtr id_c>24, který by měl zrovna tuhle hodnotu vyfiltrovat a měli by v VARCHAR2 sloupci join_column_b zbýt jen hodnoty, které lze zkonvertovat na number a regulerně se najoinovat na tbl_a.join_column_a. Tedy? Tedy pohled do exekučního plánu:

Invalid number a exekuční plán

Exekuční plán řiká: Nejprve TBL_C, vyfitrovat TBL_C, pak join na ID_C=ID_B, který odfiltruje z tabulky tbl_b řádek s textovou hodnotou a teprve pak problémový join JOIN_COLUMN_A=JOIN_COLUMN_B. Ošem tam již je řádek 23 s hodnotou “au” odfiltrován, takže ANO, sql select funguje.

No dobře, a co jiné pořadí joinování a filtrování :

Invalid number a exekuční plán

Hintem jsem si vynutil přesně obrácené pořadí joinování tabulek a nejprve se vyjoinujou TBL_A a TBL_B na sloupci, kde je textová hodnota “au” na řádku 23, která by se vyfiltrovala až v dalším kroku, nicméně takhle to na padne na prvním joinu na ORA-01722.

Mimochodem to co vybral Oracle (join C-B-A) a to co jsem vybral já (A-B-C) má stejně odhadnutý cost, moc hezky je na tom vidět, jak je ten select nebezpečný – stačí málo, aby se rozhodl jinak.

Závěr? Prakticky žádný, jen že na tohle kurva do píči pozor, protože typicky nekontrolujeme datové typy sloupců (ve smyslu, když vidím ID řádku a vidím tam hodnoty 1,2,3,4.. nepředpokládam, že je tam VARCHAR2 a končí to hodnotou “haf” a programátor předemnou byl xindl), když denně píšeme denně hafo selectů a tohle může být, bohužel, jeden z kostilvců, který se může projevit až za delší dobu při změně exekučního plánu – v extrémním případě sundá po letech produkčního běhu select přepočet statitstik..

Tip na jednu hodně mocnou věcičku, jak tomu předejít: Vhodné nastavení PLSQL-Warnings a override warningu na error. To je imho nejlepší řešení, jak eliminovat tento problém.

 Posted by at 00:56
Sep 172012
 

Porovnat obsah dvou tabulek není nic těžkého – v té nejjednodušší podobě se udělá některá z množinových operací a výsledek je hotov. Podobně napsání scriptu na sychronizaci rozdílných tabulek není žádný challenge.  Druhá možnost jak zjistit jestli data v tabulkách jsou stejná je použít nativní funkcionalitu Oracle a package s názvem DBMS_COMAPRSION. S tímto package míří Oracle na distribuované databáze a primárním účlem je porovnávat tabulky z rozdílných databází (pochopitelně větší výkon než nejhloupější script s množinvou operací “mínus”). Pokud se však parametr databázového linku nevyplní (tzn. NULL) či se vyplní loopbackovým databázovým linkem – lze porovnávat dva objekty v téže databázi. Objekty proto, že to nezbytně nutně nemusí být tabulka, DBMS_COMPARSION porovnává:

  • Tabulky
  • Single-table pohledy
  • Materializované pohledy
  • A synonyma pro předchozí objekty, tedy tabulky, single-table pohledy a materializované pohledy.

Naopak existují také omezení, mimo logických jakým je například rozdílně nastavené NLS na lokální a cílové databázi a sloupce s informací o TIME_ZONE, jsou to také restrikce technologické, především pro porovnávané tabulky které obsahují sloupce typu: ROWID, LOBy, LONG, LONG RAW, BFILE, uživatelsky definované typy a další složité typy (XMLType apod.) Nimcméně jako při vytváření ERROR LOG tabulky je možnost přeskočit “unsupported columns” a tedy porovnat objekty s tím, že nepodporované sloupce jsou omitnuty.

Dále vždy důležité warnings: Není to transakčně konzistentní na databázích po linku – tedy pokud se po scanu první tabulky změní data, výsledek nemusí mít to co bychom čekali. Primárně je funkcionalita určena na Oracle Stremas, například na odkontrolvání složitějšího APPLY.

K čemu je to dobré, jak se to dá použít i nějaké ty konstrainy a warningy tu jsou a nyní k tomu nejzábavnějšímu. Nejprve tedy nějakou tabulku na test – zdrojová:

CREATE TABLE SOURCE_TBL (
ID NUMBER PRIMARY KEY,
ENERGY_DRINK VARCHAR(20)
);

insert into SOURCE_TBL values (1,‘Monster Energy’);
insert into SOURCE_TBL values (2,‘Rockastar’);
insert into SOURCE_TBL values (3,‘Red bull’);
insert into SOURCE_TBL values (4,‘Shock’);
commit;

A tabulka se kterou ji budeme zakládat (struktura stejná, odlišná data)

CREATE TABLE TARGET_TBL (
ID NUMBER PRIMARY KEY,
ENERGY_DRINK VARCHAR(20)
);

insert into TARGET_TBL values (1,‘Monster Energy – Ripper’);
insert into TARGET_TBL values (2,‘Rockastar – Xdurance’);
insert into TARGET_TBL values (4,‘Shock’);
insert into TARGET_TBL values (5,‘Playboy Energy Drink’);
commit;

Nyní je třeba založit objekt “COMPARSION”, podobně jako se u advisoru zakládají tasky apod. COMPARSION se vytváří pomocí procedury DBMS_COMPARSION.CREATE_COMPARSION, tedy:

BEGIN
DBMS_COMPARISON.CREATE_COMPARISON(
comparison_name=>'COMPARSION_ENERGY_DINKS',
schema_name =>'HAF',
object_name => 'SOURCE_TBL',
dblink_name =>null,
index_schema_name=>null,
index_name =>null,
remote_schema_name =>'HAF',
remote_object_name =>'TARGET_TBL',
comparison_mode =>DBMS_COMPARISON.CMP_COMPARE_MODE_OBJECT,
column_list =>'*',
scan_mode =>DBMS_COMPARISON.CMP_SCAN_MODE_FULL,
scan_percent =>null,
null_value =>DBMS_COMPARISON.CMP_NULL_VALUE_DEF,
local_converge_tag => null,
remote_converge_tag =>null,
max_num_buckets =>2,
min_rows_in_bucket =>2);
END;

První spuštení procedury v Oracle patrně potrvá poněkud déle (až minuty), databáze potřebuje založit pohledy a objekty, které v DB, které nejsou založeny by default po instalaci. A nyní k parametrům:

  • comparison_name – nic překvapivého VARCHAR2, jméno comparsion
  • schema_name,object_name,dblink_name,index_schema_name,index_name,remote_schema_name a remote_object_name asi není třeba kommentovat
  • comparison_mode – konstanta DBMS_COMPARISON.CMP_SCAN_MODE_FULL  parametr má možnou jedinou tuto hodnotu, nové mody mohou být přidány v budoucnu zní poznámka
  • column_list - sloupce na kterých se bude porovnávat, zástupný znak * pro všechny
  • scan_mode – CMP_SCAN_MODE_FULL, CMP_SCAN_MODE_RANDOM, CMP_SCAN_MODE_CYCLIC nebo CMP_SCAN_MODE_CUSTOM
  • scan_percent – není třeba porovnávat celou tabulku, lze například cyklicky či náhodně a nějaké procento
  • null_value - pokud je uvedeno, cílový sloupec s tout hodnotou “odpovídá” hodnotě null. Hodnota nahrazující null
  • local_converge_tag  a remote_converge_tag - pro Oracle Streams
  • max_num_buckets – porovnává se po částech
  • max_rows_in_bucket – maximální velkost části

Po spuštění se vytvoří COMPARSION s názvem : COMPARSION_ENERGY_DRINKS, lze si prohlédnout v pohledu USER_COMPARSIONS, kde není nic jiného než založený záznam pro každé COMPARSION a jeho parametry a sloupec  LAST_UPDATE_TIME typu timpestamp, jehož název mluví za vše.

Dále je tu pohled USER_COMPARSION_COLUMNS ve kterém jsou uvedeny sloupce ke každému COMPARSION a jestli je na nich INDEX.

A nyní zbývá spustit proceduru DBMS_COMPARSION.COMPARE k vlastnímu porovnání tabulek SOURCE_TBL a TARGET_TBL.

DECLARE
t_info DBMS_COMPARISON.comparison_type;
res BOOLEAN;
BEGIN
res:=DBMS_COMPARISON.COMPARE(
comparison_name=>'COMPARSION_ENERGY_DINKS',
scan_info =>t_info,
min_value =>NULL,
max_value =>NULL,
perform_row_dif=>TRUE);
dbms_output.put_line(case when res then 'TRUE' else 'FALSE' end);
dbms_output.put_line('loc_rows_merged '||t_info.scan_id);
dbms_output.put_line('loc_rows_merged '||t_info.loc_rows_merged);
dbms_output.put_line('rmt_rows_merged '||t_info.rmt_rows_merged);
dbms_output.put_line('loc_rows_deleted '||t_info.loc_rows_deleted);
dbms_output.put_line('rmt_rows_deleted '||t_info.rmt_rows_deleted);
END;

A výstup je následující:
FALSE
loc_rows_merged 1
loc_rows_merged 0
rmt_rows_merged 0
loc_rows_deleted 0
rmt_rows_deleted 0

False řiká, že tabulky nejsou stejné a výpis z typu comparison_type jaké rozdíly tam jsou. Oracle však reportuje v tomto typu pouze hodnoty dle bucketů, proto dle nastavení nemusí odpovidat (a typicky nebudou) počtu řádků.  Jaký je výsledek scanu (spuštěním COMPARE) lze vysledovat ve view USER_COMPARSION_SCAN:  select * from user_comparsion_scan:

Comparsion

Ještě detailnější pohled přímo na rozdílné sloupce poskytuje pohled USER_COMPARISON_SCAN_VALUE, select * from user_comparsion_scan_values:

user_comparsion_values

Posledním zajímavým pohledem je USER_COMPARSION_ROW_DIF, který zobrazuje rozdílné řádky(šikovné samo o sobě, že?). select * from user_comparsion_row_dif;

user_comparsion_row_dif

Spuštěním COMPARE je hotový scan, nejsou však provedeny žádné DML změny nad tabulkama a data jsou taková jaká byla před scanem. Od promítnutí dat je tu procedura CONVERGE, tedy:

BEGIN
DBMS_COMPARISON.CONVERGE(
comparison_name=>'COMPARSION_ENERGY_DINKS',
scan_id=>1,
scan_info=> t_info,
converge_options => DBMS_COMPARISON.CMP_CONVERGE_LOCAL_WINS,
perform_commit=>TRUE,
local_converge_tag =>NULL,
remote_converge_tag=>NULL);
dbms_output.put_line('loc_rows_merged '||t_info.scan_id);
dbms_output.put_line('loc_rows_merged '||t_info.loc_rows_merged);
dbms_output.put_line('rmt_rows_merged '||t_info.rmt_rows_merged);
dbms_output.put_line('loc_rows_deleted '||t_info.loc_rows_deleted);
dbms_output.put_line('rmt_rows_deleted '||t_info.rmt_rows_deleted);
END;

Výsledek:
loc_rows_merged 1
loc_rows_merged 0
rmt_rows_merged 3
loc_rows_deleted 0
rmt_rows_deleted 1

K parametrům DBMS_COMPARSION.CONVERGE

  • comparsion_name – jméno COMPARSION
  • scan_id – každý comparsion může mít více scannů, tedy scan_id podle kterého se bude “mergovat”
  • scan_info OUT parametr s počty záznamů a jejich operacemi – viz výstup
  • coverge_options – pokud jsou na primálním klíči rozdílné řádky v obou porovávaných tabulkách, tak která vyhraje, možné dvě hodnoty CMP_CONVERGE_LOCAL_WINS a CMP_CONVERGE_REMOTE_WINS
  • perform_commit – název mluví za sebe, ovšem commit nezbytně nutně nemusí být jeden, v závislosti na nastavení (bulky)
  • local_converge_tag,remote_converge_tag – pro Oracle Streams

A nyní ještě pohled na data:

Comparsion výsledek

Comparsion výsledek

 Paráda, ne?

 Posted by at 19:09
Jul 142012
 
Od Oracle 9i byla k dispozici funkcinalita Continuous Query Notification (CQN) a to ve formě Object Change Notification (OCN), tedy možnost navěsit se na sledování DDL změn objektu a odbírat tyto změny. Hodně by se dala tato funkce nahradit klasickým triggerem – je to zajímavé, ale podstatně zajímavější je druhá část CQN, která je dostupná až od Oracle 10g a to je Query Result Change Notification (QRCN), tedy možnost registrovat si select a navěsit se na změny dat pod tímto selectem, což by se v některých případech řešilo velice obtížně triggerama – můžeme například projoinovat několik tabulek a odbírat změny dat ve výsledku tohoto selectu. Dobrý, ne?

Důležitá poznámka k funkcinalitě QRCN – selecty lze registrovat ve dvou módech – jeden je garantovaný, kdy je notifikace vygenerována vždy, když se změní data a best-effort, kdy může být vygenerován i  falešná notfikace, což se například může udít pokud se změní data, avšak pro výsledek selectu to nemá efekt žádný (například sum(salary) a odečtení a zároven přičtení stejné částky v jedné transakci). Bohužel ne všechny selecty lze registrovat v obou módech. Vlastně je to pro best-effort hodně omezené, bohužel.

Další důležitá věc, mohou nastat události a okolnosti, které automaticky select z QRCN deregistrují k čemuž může dojít z několika důvodů – ALTER TABLE, TIMEOUT nebo ztráta opravnění. Deregistrace však vyvolá také notifikaci, tentokrát o deregistraci.

Notifikace od QRCN obahuje následující: Jméno změněné tabulky, typ (INSERT, DELETE, UPDATE..) a ROWIDs změnených dat (pokud se vejde). Mimo DML i QRCN odesílá notifikaci na ALTER TABLE, TRUNCATE TABLE, DROP TABLE a FLASHBACK TABLE.

Takže hurá na první test s QRCN:

1) Nejprve založení logovací tabulky

create table tbl_log(
id number primary key,
log_text varchar2(4000)
);

create sequence seq_log_id;

Tabulka, kam bude notifikační handler vkládat informace o přijaté notifikaci. Tabulka je pro test nejednodušší a nejrychlejší, ale v praxi by to mohlo například posílat email. A sekvence pro generování ID.

2) Procedura handleru pro přijetí notifikace

CREATE OR REPLACE PROCEDURE prc_qn_handler(ntfnds IN CQ_NOTIFICATION$_DESCRIPTOR)
IS
log_text varchar2(4000);
BEGIN
log_text:='Registration ID='||ntfnds.registration_id;
log_text:=log_text || ', Transaction ID='||ntfnds.transaction_id;
log_text:=log_text || ', Dnbame='||ntfnds.dbname;
INSERT INTO tbl_log VALUES(seq_log_id.nextval,log_text);
commit;
END;

Procedura je spuštěna pokaždé, když background job Oracle pošle na základě události (změna dat, ALTER TABLE ..) notifikaci. Procedura musí mít jako jediný paramatr CQ_NOTIFICATION$_DESCRIPTOR, tedy notifikaci. A je třeba jí zaregistrovat, viz dále. Tělo je jen na ukázku – nic světoborného, vybere data z přijmutého parametru a insertne je do našeho logu TBL_LOG. Přijatá notifikace typu CQ_NOTIFICATION$_DESCRIPTOR obsahuje více informací než je v mém handleru, například ROWID změněných řádků či tabulku

2) Testovací tabulky s datama

create table tbl_orders
(
id number primary key,
order_type varchar2(30),
ordered date default sysdate
);
create table tbl_orders_items
(
id number primary key,
order_id number,
order_item varchar2(30),
CONSTRAINT fk_for_key FOREIGN KEY (order_id) REFERENCING tbl_orders(id)
);
insert into tbl_orders values (1,'order_1',sysdate);
insert into tbl_orders_items values (1,1,'order_1_item_1');
insert into tbl_orders_items values (2,1,'order_1_item_2');
insert into tbl_orders_items values (3,1,'order_1_item_3');
insert into tbl_orders values (2,'order_2',sysdate);
insert into tbl_orders_items values (4,2,'order_2_item_1');
insert into tbl_orders_items values (5,2,'order_2_item_2');
insert into tbl_orders values (3,'order_3',sysdate);
insert into tbl_orders values (4,'order_4',sysdate);
insert into tbl_orders_items values (6,4,'order_4_item_1');
commit;

K tomu asi není co dodat, čistě tabulky a data nad kterými pak bude zaregistrovaný select pro QRCN. A pro představu jak data vypadají:

Data pro QRCN

Data pro QRCN

3) Select, který zaregistrujeme pro QRCN

select ord.id,order_item from tbl_orders ord,tbl_orders_items items where ord.id=items.order_id and ord.id=1;

Select, který budeme registrovat pro query

Select, který budeme registrovat pro query

4) Zaregistrování query jako QRCN

Máme hotový log, připravená data, připravený select i handler a tedy zbývá poslední krok k tomu, aby to celé fungovalo – zaregistrování selectu jako QRCN.

declare
v_cn_addr CQ_NOTIFICATION$_REG_INFO;
id_number number;
cur_reg sys_refcursor;
begin
v_cn_addr:=CQ_NOTIFICATION$_REG_INFO('azor.prc_qn_handler',
dbms_change_notification.QOS_ROWIDS+dbms_change_notification.QOS_BEST_EFFORT,
0);
id_number:=DBMS_CQ_NOTIFICATION.NEW_REG_START(v_cn_addr);
open cur_reg for
select id,order_item,dbms_cq_notification.CQ_NOTIFICATION_QUERYID from tbl_orders ord,tbl_orders_items items
where ord.id=items.order_id and ord.id=1;
close cur_reg;
DBMS_CQ_NOTIFICATION.REG_END;
dbms_output.put_line('DBMS_CQ'||id_number);
end;

Kroky jsou následující – vytvořit objekt CQ_NOTIFICATION$_REG_INFO, kterému předat jako parametr handler funkci (v mém případě prc_qn_handler), po té zavolat DBMS_CQ_NOTIFICATION.NEW_REG_START, které se předá vytvožený objekt (v mém případě v_cn_addr) a po té následujie otvirání kurzorů, jejihž selecty chceme registrovat do daného QRCN a po té zavolání DBMS_CQ_NOTIFICATION.REG_END.

5) Update dat

Fajn, všehno je hotovo a nyní by to chtělo vyzkoušet, tedy dva různé updaty:

update tbl_orders_items set order_item='updated' where order_id=1;
commit;
update tbl_orders_items set order_item='updated2' where order_id=2;
commit;

6) Select do logu

Select do logu notifikaci

Super, ne? Updatem číslo 1, který byl s QRCN registrovaným pod ID =3 byla vyvolána notfikace, ta předána mému handleru prc_qn_handler jež zapsal do logu TBL_LOG.
Na škole nám vždycky vloukali do hlavy větu “A k čemu je to dobré?” (ačkoliv většina toho, co nás učili nebyla nikdy k ničemu dobrá). Já myslim, že si použití najde každý sám a ačkoliv se to dá použít i pro reporting/monitoring systému, tak primárně Oracle míří na takové featury jako je nacachování dat na aplikační server a registorvání QRCN, které v případě změny odešle aplikačnímu serveru notifikaci a invaliduje cache/donutí k opětovénému fatche dat.


 Posted by at 11:38
Jul 012012
 

Minule jsem se zmínil, že Oracle neumožňuje tabulku véci pod rozdílnými edicemi (narozdíl od funkcí, procedur, view..). Úplná pravda je taková, že v Oracle lze mít v tabulce několik verzí řádků, ovšem není použita featura “EDITIONS”, ale je použito něco, čemu Oracle řiká Workspace, manuál zde. Je to funkcionalita o které jsem dlouho nevěděl a myslím, že je třeba si to zkusit! Co se týče workspaces v Oracle datábázi jsem byl jsem do dneška panic ;)

Workspace podporuje hiearchii (alternativně až acyklický graf,wow), názvy workspace jsou case-sensitive, maximální délka názvu workspace je 30 znaků, ale především workspace pod kterou jede aktuálně databáze se jmenuje LIVE.

Funkcinality workspaces: každé workspace může obsahovat implicitní a explicitní savepoints (místa kam lze odrolovat změny), workspace lze mergovat (hlavní funkcinalita) – samozřejmě je zde podpora řešení konfilktů.

Tabulky ve workspace mají na sobě trigger, který verzuje do tabulek s názvem <table_name>_LT z čehož plyne nějaké omezení – menší možný počet sloupců v workspacované tabulce, název tabulky může být jen 25 znaků apod. Sloupce nemohou začítnat prefixem WM_ ani WM$. Název tabulky nemůže končit surfixem _G.

Zajímavé je DDL – mimo některých omezení se DDL začinají příkazem BeginDDL a po té se i commitují příkazem CommitDDL – wow, commitování DDL.

Nad tabulkou ve workspace nejsou podporovány statement-level triggery. Materializované pohledy jen s FULL refresh.

K tabulce ve workspace vzniknou view, ty významnější jsou:

Jméno Význam
“tableName” Workspacovaná tabulka
“tableName”_LOCK Tabulka s LOCKama, při mergování workspaces
“tableName”_CONF Tabulka s conflictama při mergování
“tableName”_DIFF Rozdíl tabulky pod ruznýmí workspaces
“tableName”_HIST Historie, pokude je enabled

Celé ovládání je pomocí balíku DBMS_WM a Workspace vyžadují i nějaká ta práva (CREATE_WORKSPACE, CREATE_ANY_WORKSPACE, MERGE_WORKSPACE,FREEZE_WORKSPACE..). Zde bacha na VPD a právo MERGE_WORKSPACE.

Teorie bylo vcelku dost, tak hurá do hraní:

1) Založení usera a nagrantování mu práv

CREATE USER azor_workspace IDENTIFIED BY azor_workspace;
GRANT connect, resource, create table, merge_any_workspace,
create_any_workspace, remove_any_workspace, rollback_any_workspace to azor_workspace;
GRANT execute on dbms_wm to azor_workspace;

2) Založení tabulky pod uživatelem azor_workspace

CREATE TABLE TBL_WORKSPACE(
id number primary key,
 a  varchar2(50),
 b  varchar2(50),
 c  varchar2(50)
);

3) Založení view s historii

EXECUTE DBMS_WM.EnableVersioning ('TBL_WORKSPACE', 'VIEW_WO_OVERWRITE');

Zde to vyhučí, pokud vaše tabulka nemá primární klíč, je tedy nutný primární klíč. Příkaz zcela logicky musí založit i nějaké ty objekty, proto je dobré se před dalším krokem v příkladě z manualu ještě podívat, co že to bylo založeno:

 select * from all_objects order by created desc

A je toho hodně ! Takže to pojďme nějak rozbrat:

1) Z mé tabulky TBL_WORKSPACE je nyní VIEW 

2) V mém schématu existují dvě fyzické tabulky:

TBL_WORKSPACE_AUX -  patrně udržuje informace o verzích



TBL_WORKSPACE_LT – moje verzovaná tabulka, mimo mých sloupců ID,A,B, a C obsahuje ještě repozitorovací sloupce: VERSION, CREATETIME, RETIRETIME, NEXTVER, DELSTATUS a LTLOCK

3) Byla založená následující view: TBL_WORKSPACE_DIFF, TBL_WORKSPACE_LOCK,TBL_WORKSPACE_PKC, TBL_WORKSPACE_PKD,TBL_WORKSPACE_PKDC,TBL_WORKSPACE_PKDB, TBL_WORKSPACE_CONF,TBL_WORKSPACE_BPKC, TBL_WORKSPACE_HIST, TBL_WORKSPACE_CONS, TBL_WORKSPACE_MW a TBL_WORKSPACE_BASE, O některých se zatím dokumentace nezmiňovala.

4) Byly založeny 3 INSTEAD OF triggery nad view TBL_WORKSPACE_BASE

OVM_Delete_1, OVM_Insert_1 a OVM_Update_1, kód nejzajímavějšího z nich Update je zde: OMW_Update_1.sql

 V kódu je zajímavé použití nvl(sys_context(‘lt_ctx’, ‘state’), ‘LIVE’) z kterého je patrné, že aktuální verze workspace je uložena v contextu lt_ctx a k řízení je použito baliku wmsys.

4) Insert řádků do workspacované tabulky:

INSERT INTO TBL_WORKSPACE VALUES (1,'a1','b1','c1');
INSERT INTO TBL_WORKSPACE VALUES (2,'a2','b2','c2');
INSERT INTO TBL_WORKSPACE VALUES (3,'a3','b3','c3');
commit;

Zajímavé je flow – mnou založená tabulka TBL_WORKSPACE je nyní view, které je definováné jako select z view TBL_WORKSPACE_BASE, které je definováno jako view z tabulky TBL_WORKSPACE_LT. A tedy update/insert/delete nad TBL_WORKSPACE je nad view, které míří na view TBL_WORKSPACE_BASE na kterém jsou navěšeny již zmíněné triggery OVM_Delete_1, OVM_Insert_1 a OVM_Update_1 insertující data do fyzické tabulky TBL_WORKSPACE_LT.

Po insertu jsou záznamy v tabulce WORKSPACE_LT a mimo mých hodnot obsahují verzi (0), timestamp (systimestamp with local zone), nextver (-1), delstatus(10) a údaj o locku TLLOCK s pravzláštní hodnotou “*-1,-9,-1*$#AZOR_WORKSPACE$#”.

5) Vytvoření workspace

EXECUTE DBMS_WM.CreateWorkspace ('azor_workspace_1');

Ještě před dalším krokem jsem udělal select * from all_workspaces;  V tomto pohledu jsou dvě workspace – LIVE jako default databáze a moje azor_workspace_1 a informace o nich – kdo je parent, kdy bylo založeno, jestli je FREEZE, FREEZE_WRITER, LOCKMODES apod.

A dle manuálu založím ještě jeden:
EXECUTE DBMS_WM.CreateWorkspace ('azor_workspace_2');

6) Přepnutí do workspace  azor_workspace_1 a práce v něm

 EXECUTE DBMS_WM.GotoWorkspace ('azor_workspace_1');

A nyní nějaké ty úpravy, zcela logicky – jeden update, jeden delete, jeden insert ;)

UPDATE TBL_WORKSPACE SET a='a2_workspace_1',b='b2_workspace_1',c='c2_workspace_2' WHERE id=2;
DELETE FROM TBL_WORKSPACE WHERE id=3;
INSERT INTO TBL_WORKSPACE VALUES(4,'a4_workspace_1','b4_workspace_1','c4_workspace_1');
commit;

A nyní je hodně zajímavý pohled do repozitorovacý tabulky TBL_WORKSPACE_TL:

Workspace
Workspace

 

7) Přepnutí do workspace azor_workspace_2 a práce v něm

 EXECUTE DBMS_WM.GotoWorkspace ('azor_workspace_2');

V druhém workspace, tedy dědím řádky od roota (LIVE verze) a nevidím změny provedené ve druhém workspace:

A nyní nějaké změny:
INSERT INTO TBL_WORKSPACE VALUES(4,‘a4_workspace_2′,‘b4_workspace_2′,‘c4_workspace_2′);
– Záznam s privátním klíčem ID=4 je i v druhém workspace
INSERT INTO TBL_WORKSPACE VALUES(5,‘a5_workspace_2′,‘b5_workspace_2′,‘c5_workspace_2′);
– Záznam s ID=5 není ve druhém workspace
UPDATE TBL_WORKSPACE SET a=‘a2_workspace_2′,b=‘b2_workspace_2′,c=‘c2_workspace_2′ WHERE id=2;
– Tento záznam byl updatován v obou workspaces
DELETE FROM TBL_WORKSPACE WHERE id=3; commit;
–Tento řádek byl také smazán ve druhém workspace

7) Data – LIVE, workspace 1,2 verze a pohled do *_LT tabulky

Je čas podívat se jak vlastně nyní vypadají verze tabulek pod workspace LIVE, azor_workspace_1 a azor_workspace_2 a pochopitelně i pohled na celé repozitory ;)

Pohled na data ve všech workspace a repository
Pohled na data ve všech workspace a repository

8) Rozdíl různých workspaces

 Co dál? V balíku DBMS_WM je opravdu velká řádka funkcí, zajímavých i nezajímavých (overheady typu “RenameSavepoint”, “DropSaveopint”, které funkčnost vyžaduje, zábava však s nima není). V tomto stádiu je pro mně nezajímavější jednoznačně procedůra SetDiffVersions, která jako výsledek vrátí rozdíly mezi jednotlivými verzemi tabulek (či jejich savepointů), so ukázka:

begin
DBMS_WM.SetDiffVersions(
workspace1=>'azor_workspace_1',
workspace2=>'azor_workspace_2');
end;

Výsledek je možné vyzvednout si v pohledu _DIFF, pro mne tedy:

select * from TBL_WORKSPACE_DIFF;

*_DIFF pohled na workspace

*_DIFF pohled na workspace

A to by bylo asi pro tento článek vše. Ačkoliv s workspace budu ještě pokračovat – schválně jsem vynechal merge, protože si chci užít ještě legraci s ostatníma funkcema/procedurama než to začnu mergovat a řešit koflikty.

Workspace jsou kažodpádně hodně zajímavá funkcinalita a je zajímavé, že jsem ji vlastně ještě nikdy nikde neviděl, kromě manuálu ;) A kam dál? DBMS_WM Package: Reference.

 Posted by at 14:08
Dec 162011
 

Virtual Private Database čili Fine Grained Access Control (FGAC) je funkcionalita, která umožňuje na úrovni databáze definovat filtry pro operace SELECT, INSERT, UPDATE a DELETE. Typickým příkladem, který uvádí Oracle je lékařské aplikace, kdy je možné vrátit lékaři na základě jeho ID pouze záznamy jeho pacientů v případě, že zavolá select * from vsechni_pacienti; Podobně to funguje také s UPDATE a DELETE. Výhodou použití VPD je pochopitelně bezpečnost, která je mnohem lepší než omezení na straně klienta. Další výhodou je jednodušší správa aplikace a možnost lepšího zabezpečení již existujících aplikací. Výhod spousta, nevýhod méně – tedy reálně použitelné.

Pro použití VPD je klíčový balík dbms_rls (ROW LEVEL SECURITY), který možňuje zaregistrování funkcí (nebo i skupin funkcí od 11g2) na tabulky na které je potřeba použití funkcionalitu Fine Grained Access Control. Pomocí balíku dbms_rls se registruje vlastní funkce, která má dva vstupní parametry typu varchar2 (owner a jméno objektu) a jako výstup vrací where podmínku, která omezuje danému uživateli sadu záznamů.

Jak to interně funguje?  Po zavolání select (update, delete) Oracle zavolá VPD funkci a spouštěný select “obalí” do závorek a doplní tam další WHERE a text obdržený právě z VPD funkce.

Virtual Private Database (respektive aplikace RLS politiky) mimochodem nefunguje na SYS uživatele, na což je dobré poukázat pro nás, co na své zkušební databázi jedeme pod SYSem (ano nemá se to, ale když si člověk zkouší updaty systémových tabulek, co mu zbývá). Tedy je nutné se připojit jako nějaký člověk (SYS je bůh).

CREATE USER AZOR IDENTIFED BY AZOR DEFAULT TABLESPACE HAF;
ALTER USER AZOR QUOTA UNLIMITED ON HAF;
GRANT RESOURCE TO AZOR;
GRANT CREATE SESSION TO AZOR;

To by byla nějaká ta teorie a nyní nějaká ukázka:

1) Nejprve založím nějakou tabulku

CREATE TABLE TBL_TEST_VPD AS SELECT ROWNUM colum,username FROM
all_users,
(SELECT 1 FROM dual CONNECT BY LEVEL<100);

Bez VPD obsahuje tabulka 4200 řádků – 42 uživatelů cross join 100 záznamů.

2) Založím funkci přesně dle potřebné specifikace (je to podobné jako s funkcí pro validaci správně silného hesla, pokud je jejiná bezpečnostní politika než kterou má Oracle)

CREATE OR REPLACE FUNCTION fce_vpd_policy (owner IN VARCHAR2, object_name IN VARCHAR2) RETURN
VARCHAR2
IS
BEGIN
RETURN 'username='''|| USER ||'''';
-- zde by to melo byt s CONTEXTU, ale letnost je lenost ;)
END;

Mimochodem je to ta nejminimálnější funkce, jaká má smysl a se kterou to bude fungovat.

3) A přidání policy funkce :

BEGIN
dbms_rls.add_policy(
'AZOR',  -- vlastník tabulky
'TBL_TEST_VPD',-- jméno tabulky
'MOJE_POLICY', -- jméno policy
'AZOR', -- vlastník VPD funkce
'FCE_VPD_POLICY', -- jméno vpd funkce
'SELECT' -- pouze pro select
);
END;

Spuštěno pod SYS uživatelem, dbms_rls jsem novemu uživateli nenagrantoval.

4) A nyní test:

Pod uživatelem AZOR:

SELECT COUNT(*) FROM TBL_TEST_VPD; -- pouze záznamy s username=AZOR, jinak je tam 4200 záznamů
100

Reálně je však spouštěno SELECT COUNT(*) FROM TBL_TEST_VPD WHERE username='AZOR'S tím, že modrou část doplní Oracle z VPD funkce.

Bylo to jen pro select, tedy si muzu vsechny zaznamy prohlednout updatem a zase je rollbacknout (proto by to nemelo byt pouze na select)

UPDATE TBL_TEST_VPD SET username='AZOR'; rollback;
4200 rows updated. rollback completed.

A nyní ještě test pro SYS, pro něj to nefunguje:

SELECT COUNT(*) FROM AZOR.TBL_TEST_VPD;
4200

Pozor: Tabulka s aplikovanou VPD nejde do koše v případě dropu.

 Posted by at 23:36
Dec 072011
 

Použiváte CACHE parametr při vytváření sekvence? Pokud zadáte místo defalutního NOCACHE hodnotu CACHE n Oracle automaticky načte poslední poslední použité číslo a reservuje pro sebe n hodnot, které může daný proces volně používat a z toho plyne i výkonová výhoda – vícenásobné použití sekvence bez nutnosti přistupovat často na medium. Logicky Oracle doporučuje používat vždy tuto hodnotu na RAC (Real Aplication Cluster), kde o může o blok soupeřit více procesů a kde je nutné pečlivě ladit výkon. Menší nevýhoda CACHE je, že i když dáte ORDER čísla budou sice stále zasebou, ale mohou mít mezi sebou mezery. A jak k tomuto tématu řiká Tomas Kyte “mezery nevadí”. Důvod mezer je právě funkcionalita kešování hodnot – Oracle si vymezí n hodnot a na mediu inkrementuje sekvenci o n hodnot. V paměti je pak hodnota inkremetována a až do dalšího zápisu při hodnodě n je držena pouze v paměti a není nikde zapisována. V případě výpadku instance, flush SGA apod pak dojde v “díře” mezi hodnotami.

Kdy používat CACHE? Při logovování, ve statistikách, v tabulkách kde je primarání klíč generování sekvencí ..

Kde nepoužívat? V místech, kde z pohledu výpočtu NESMÍ být mezery a nebo v tabulkách tybu konfiguračních, kde je pár stovek/tisíc záznamů a kde neřešíme výkon ale raději dáme přednost koukání na hezky čísla za sebou ;)

Na co pozor? MAXVALUE musí být nyní počet_chtěných hodnot krát velikost CACHE.

I když použití CACHE parametru se hodí na zatížený systém, lze si benefit této featury vyzkoušet i na instanci Oracle, která není pod žádným zatížením :

  • Nejprve vytvoříme testovací sekvence – 2 na 20 je asi 1 milion a tedy vytvoříme 24 sekvencí (NOCACHE+23 krát budeme násobit velikost CACHE parametru)

1) SEQUENCE bez CACHE:
CREATE SEQUENCE SEQ_NOCACHE MINVALUE 1 MAXVALUE 99999999999999 INCREMENT BY 1 START WITH 1 NOCACHE ORDER NOCYCLE;
sequence SEQ_NOCACHE created.
Elapsed: 00:00:00.140

2) SEQUENCE CACHE 20:
CREATE SEQUENCE SEQ_NOCACHE MINVALUE 1 MAXVALUE 99999999999999 INCREMENT BY 1 START WITH 1 CACHE 20 ORDER NOCYCLE;
sequence SEQ_NOCACHE created.
Elapsed: 00:00:00.140

3) SEQUENCE CACHE 200:
CREATE SEQUENCE SEQ_NOCACHE MINVALUE 1 MAXVALUE 99999999999999 INCREMENT BY 1 START WITH 1 CACHE 200 ORDER NOCYCLE;
sequence SEQ_NOCACHE created.
Elapsed: 00:00:00.140

4) SEQUENCE CACHE 20000:
CREATE SEQUENCE SEQ_NOCACHE MINVALUE 1 MAXVALUE 99999999999999 INCREMENT BY 1 START WITH 1 CACHE 20000 ORDER NOCYCLE;
sequence SEQ_NOCACHE created.
Elapsed: 00:00:00.140

5) Kód k testování rychlosti:

DECLARE
n_test NUMBER;
BEGIN
FOR i IN 1..3999999 LOOP
n_test:=SEQ_NOCACHE.NEXTVAL;
END LOOP;
END;

Sekvence Cache Čas
SEQ_NOCACHE NOCACHE 00:14:38.891
SEQ_CACHE20 20 00:02:46.547
SEQ_CACHE200 200 00:02:10.532
SEQ_CACHE20000 20000 00:02:07.125

 

Všimnou je dobré si například toho, jak Oracle zachází sám s CACHE u systémových sekvencí – bez keše jsou sekvence pro HR schéma, konfigurace Oracle Streams a to je téměř vše u ostatních systémových sekvencí Oracle databáze CACHE má – od 2 (Logminer, Warehouse) až po 10000 (Advence Query)

 Posted by at 00:25