Unde-i dom'le "library cache"-ul ăsta?
Ați folosit vreodată "Google Earth"? E o aplicație pe care o putem utiliza pentru a explora virtual tot felul de locații geografice, ca și când le-am observa din satelit. Foarte frumos, dar ce legătură are "Google Earth" cu ce discutăm noi aici? Niciuna! Vreau doar să fac o analogie. Îmi place efectul acela de "zoom in". Inițial apare planeta Pământ, rotindu-se frumos în jurul axei proprii. Dăm o căutare să zicem după "Iași, România", colimatorul este poziționat pe locația indicată, iar prin efectul de "zoom-in" de care vă spuneam trecem prin Europa, România și, în sfârșit, orașul Iași cu străzi, clădiri și tot "tacâmul".Haideți să ne imaginăm o aplicație similară, doar că în loc de locații geografice avem de-a face cu structuri interne ale serverului Oracle. N-am nici cea mai vagă idee cum ar trebui să se numească această aplicație, dar cert e că "Google Oracle" sună ca naiba. Hmmm... Gata, știu! În același spirit, îi putem spunem "Oracle Oracle"! Sau nu, ca să-i dăm o aură de mister îi zicem "2Oracle", pentru că 2 citit ca "to" ar putea sugera ideea de destinație. În fine, chiar nu are nici o importanță numele.
Inițial, de departe, ar trebui să ne apară o struțo-cămilă cu fișiere de date, structuri de memorie și procese, toate interconectate într-un soi de reprezentare abstractă, pentru unii lipsită de gust, pentru alții fermecătoare.
Ne interesează mai multe despre "library cache", așa că vom da frumușel o căutare după acest concept. Efectul "zoom-in" intră în acțiune: trecem prin SGA (System Global Area), apoi ajungem la o sub-zonă de memorie denumită "shared pool" și, mai departe, prin aceasta, la ceea ce ne interesează pe noi, zona "library cache". La acest nivel de detaliu putem să analizăm mai bine cum este alcătuită această zonă și cum funcționează ea.
Figura 1 - Zona "library cache" în SGA |
Dimensionarea "library cache"-ului
Ne-am obișnuit cu o flexibilitate foarte mare în ceea ce privește configurarea server-ului Oracle. Putem să-i zicem câtă memorie să folosească pentru "buffer cache", care să fie numărul maxim de procese și câte și mai câte. Prin urmare, n-ar fi chiar o aberație să ne gândim că ar putea exista un parametru prin intermediul căruia să putem specifica dimensiunea "library cache"-ului. Căutăm repede în documentație și ne lămurim. Un astfel de parametru nu există.Să ne uităm cu atenție la "Figura 1"! Observăm că "library cache-ul" este parte din "shared pool", alături de alte câteva sub-zone de memorie: cea alocată dicționarului, apoi "result cache"-ul de pe server, o zonă rezervată și structuri interne fixe. Nu putem influența dimenșiunea "library cache"-ului direct, dar o putem face indirect, prin modificarea unor parametri conecși.
Să analizăm logic (sau băbește). Unde este localizat "library cache"-ul? În "shared pool". Putem specifica dimensiunea "shared pool"-ului? Desigur! Asta înseamnă că avem o primă pârghie pe care o putem folosi pentru a influența dimensiunea "library cache"-ului. Un "shared pool" mare ne asigură, teoretic, premisele unui "library cache" mai generos.
Figura 2 - Configurarea "shared pool"-ului |
Modelul de alocare a memoriei
Deși ne-am propus să discutăm despre "library cache", alocarea memoriei în cadrul acestei structuri nu poate fi separată de mecanismul general utilizat pentru întregul "shared pool".Câteva explicații lămuritoare sunt necesare. În primul rând, Oracle definește conceptul de "granulă". Aceasta reprezintă unitatea de memorie utilizată în mecansimele de alocare/de-alocare a memoriei SGA, deci, implicit, și a "shared pool"-ului. Dimensiunea granulei poate să fie diferită de la un sistem la altul, însă o putem afla oricând, dat fiind că ea este expusă în câteva view-uri sistem.
SQL> select distinct granule_size from v$sga_dynamic_components; GRANULE_SIZE ------------ 16777216 SQL> SELECT * FROM v$sgainfo WHERE name = 'Granule Size'; NAME BYTES RES -------------------------------- ---------- --- Granule Size 16777216 NoAlocarea de memorie prin granule nu trebuie confundată cu alocarea în cadrul acestor granule. Conceptul de "granulă" este interesant doar în contextul alocării inițiale de memorie pentru anumite "buffer"-e, eventual în operațiile ulterioare de redimensionare a acestora.
Figura 3 - Alocarea memoriei prin granule |
SQL> select current_size/1024/1024 size_M, current_size/granule_size granules 2 from v$sga_dynamic_components 3 where component='shared pool'; SIZE_M GRANULES ---------- ---------- 1056 66Dar, odată alocată această memorie, felul în care intern ea este mai departe gestionată e altă mâncare de pește.
Pentru a ne lămuri mai bine vom apela la puțină "magie neagră". Vom trage o ochiadă într-o tabelă X$, dar, mare atenție, este periculos a o interoga pe sisteme în producție! Tabela se numește X$KSMSP, iar denumirea ciudată vine de la "Kernel Service Memory/Management Shared Pool". Este periculos a o interoga pe sisteme puternic tranzacționate deoarece, ori de câte ori facem asta, Oracle va activa o serie de "latch"-uri, care mai departe vor bloca celelalte sesiuni care au treabă cu "shared pool"-ul. Această tabelă conține bucățelele de memorie alocate în cadrul "shared pool"-ului. E vorba de "cealaltă mâncare de pește" de care vă vorbeam: avem 66 de granule alocate, dar la nivelul mecanismului de alocare intern specific "shared pool"-ului discutăm de o împărțire total diferită. Haideți să vedem câte astfel de segmente de memorie avem alocate:
SQL> select count(*) from x$ksmsp; COUNT(*) ---------- 322119Avem peste trei sute de mii de astfel de segmente, mare parte din ele cu dimensiuni diferite, așa cum ne arată, de altfel, și interogarea de mai jos:
SQL> select count(distinct KSMCHSIZ) from x$ksmsp; COUNT(DISTINCTKSMCHSIZ) ----------------------- 1247Deci, "shared pool"-ul pe care îl analizăm (cel de pe baza mea de date) operează cu peste o mie de dimensiuni diferite de alocare. Ne putem face o idee și asupra tipurilor de zone de memorie alocate în "shared pool":
SQL> select ksmchcls, sum(ksmchsiz) from x$ksmsp group by ksmchcls; KSMCHCLS SUM(KSMCHSIZ) -------- ------------- recr 283809640 freeabl 521013344 R-freea 6240 perm 175289832 R-free 54566920 free 55827344 6 rows selected.Avem segmente marcate ca "free" ce pot fi folosite în alocări ulterioare, segmente de tip "perm", adică permanente, ce corespund structurilor interne fixe (vezi figura 1) și segmente "recr", adică recreabile, ce conțin informații ce pot fi recreate, așa cum este de pildă cazul datelor din dicționar (pot fi recitite de pe disc) sau a cursoarelor (SQL-urile pot fi reparsate). În ciuda denumirii lor, segmentele "freeabl" reprezintă de fapt memorie alocată de anumite module ale "kernel"-ului Oracle și care pot fi eliberate doar de către aceste module. Segmentele care sunt prefixate cu "R" sunt asociate zonei rezervate a "shared pool"-ului.
Figura 4 - Alocarea în cadrul "shared pool" |
"Library cache"-ul și mai de-aproape
E timpul să analizăm mai în detaliu conținutul "library cache"-ului. Punem la treabă ipotetica noastră aplicație "2Oracle" și mărim imaginea, tocmai pentru a distinge mai bine. Facem ochii cât cepele și ce vedem acolo? Niște structuri ciudate, arborescente, care ar reprezenta cică așa-numitele cursoare. Figura de mai jos prezintă schematic o astfel de structură.Figura 5 - Un model de cursor SQL, după parsare |
Principalul motiv pentru care Oracle a pus la punct acest mecanism este acela de a refolosi aceste cursoare, în cazul executării ulterioare a acelorași comenzi SQL. Singura modalitate prin care Oracle se poate prinde dacă are sau nu de-a face cu aceeși comandă SQL constă în preluarea textului comenzii și compararea acestuia cu cel stocat în library cache. Problema e că operația de identificare a unui cursor în "library cache" pentru o comandă dată se poate dovedi extrem de costisitoare, mai ales dacă avem de-a face cu un "library cache" generos, care permite stocarea a numeroase cursoare. Spre exemplu, pe baza mea de date sunt peste 6000:
SQL> select count(1) from v$sqlarea; COUNT(1) ---------- 6010Este clar că o operație de căutare în cadrul unei astfel de liste, înainte de a executa orice comandă SQL, nu poate fi fezabilă, mai ales dacă introducem în ecuație și mediul concurențial. Prin urmare, băieții de la Oracle vin cu un mecanism ingenios, prin care textului comenzii SQL i se aplică o funcție "hash", iar rezultatul este folosit mai departe pentru a indexa cursorul părinte corespunzător. Deoarece pentru comenzi SQL distincte funcția "hash" poate returna același rezultat, rezultă că putem avea SQL-uri diferite reunite sub umbrela aceleași chei de indexare. În termeni de programare discutăm practic de un "array"/tablou asociativ (sau hash-table), în care cheile sunt date de "hash", iar valoarile sunt de fapt niște "array"-uri care conțin cursoarele părinte. Aceste "array"-uri le veți găsi menționate în documentația Oracle ca "library cache hash buckets/chains".
Figura 6 - Modelul de grupare a cursoarelor |
Succesul mecanismului de mai sus se bazează pe bine-cunoscutul conceptul "divide et impera". Practic, atunci când Oracle are de executat o comandă SQL, în loc să o caute într-o listă lungă cât o zi de post, pur și simplu va genera o cheie "hash" pentru textul comenzii, "hash" cu care va accesa "bucket"-ul corespunzător. Va căuta apoi dacă există un cursor părinte pentru acea instrucțiune SQL, dar doar în cadrul respectivului "bucket". Simplu și eficient!
Puțină statistică
Pentru a evalua eficiența "library cache"-ului, Oracle ne pune la dispoziție un view special, denumit V$LIBRARYCACHE. În cadrul acestuia operăm cu următoarele noțiuni:- GETS
- GETHITS
- PINS
- PINHITS
- RELOADS
- INVALIDATIONS
SQL> select gets, gethits, pins, pinhits, reloads, invalidations 2 from v$librarycache 3 where namespace = 'SQL AREA'; GETS GETHITS PINS PINHITS RELOADS INVALIDATIONS ---------- ---------- ---------- ---------- ---------- ------------- 2505073 2477645 225079378 224926389 26136 22624Haideți să vedem care este semnificația cifrelor de mai sus. Ori de câte ori dorim să executăm o comandă SQL nouă, server-ul Oracle trebuie să verifice dacă nu cumva a executat deja această comandă și are reprezentarea ei gata parsată în "library cache". Cu alte cuvinte, procesul server care dorește a executa comanda în cauză, întreabă "library cache"-ul: hei, nu cumva ai deja comanda cu hash-ul cutare? Asta se traduce într-un nou "GET" și va fi consemnat ca atare în coloana "GETS" a view-ului V$LIBRARYCACHE. Dacă este găsită comanda de executat în "library cache", atunci discutăm de un "GETHIT", care va incrementa valoarea coloanei "GETHITS". Discutăm în acest caz de un posibil "soft parse". Spun posibil pentru că, așa cum vom vedea imediat, este prematur a ne pronunța încă. Dacă nu găsim un astfel de cursor în "library cache", atunci discutăm de un "hard parse", în acest caz Oracle fiind nevoit să treacă prin tot procesul de parsare/optimizare.
Bun, care-i șmecheria cu "PIN"? Un "pin" intervine ori de câte ori comanda trebuie efectiv executată. Asta înseamnă că, odată identificat cursorul parinte, sub acesta trebuie găsit cursorul copil corespunzător, care implică mai departe găsirea și inspectarea zonei de memorie ce conține informații cu privire la mediul folosit de optimizor (pentru a vedea dacă se potrivesc), informații cu privire la variabilele de tip "bind", dar și planul de execuție corespunzător. Cererea de a executa instrucțiunea se traduce într-un nou "PIN" și va incrementa valoarea coloanei "PINS". Dacă toate informațiile specifice cursorului copil sunt găsite în "library cache" atunci discutăm de un "PINHIT".
După cum se poate observa și în figura 5, informațiile asociate cursorului copil sunt stocate în segmente de memorie diferite, prin urmare, datorită mecanismului de alocare/dealocare specific "shared pool"-ului, există posibilitatea ca anumite astfel de segmente să nu mai existe. Spre exemplu, se poate întâmpla ca planul de execuție ce corespunde instruncțiunii în cauză să fi fost înlăturat din memorie pentru a se face loc unor noi alocări. În acest caz, discutăm de un "RELOAD", adică procesul server care este responsabil de executarea instrucțiunii s-a înnecat ca țiganul la mal. A trecut prin toată tevatura de identificare a cursorului părinte, a identificat si un cursor copil candidat, dar, ghinion, acesta nu avea tot ce-i trebuie, făcându-l numai bun de aruncat la gunoi. O cifră mare pe coloana "RELOADS" raportată la numărul de "PINS", reprezintă o primă indicație a faptului că memoria necesară "library cache"-ului nu este suficientă.
În sfârșit, ajungem la "INVALIDATIONS". Fiecare cursor dispune de informații cu privire la obiectele de care depinde. Spre exemplu, comanda "SELECT COL1, COL2 FROM T" depinde de tabela "T", iar un cursor ce provine dintr-o execuție precedentă a acestei instrucțiuni va ști lucrul ăsta. Dacă aducem modificări de structură tabelei "T", atunci cursorul corespunzător va fi marcat ca invalid și nu va mai putea fi reutilizat. În acest caz, coloana "INVALIDATIONS" va reflecta acest lucru. Un număr mare de invalidări indică, de obicei, o frecventă utilizare de comenzi DDL care afectează cursoarele dependente (modificări de structură, revocare de drepturi etc.).
Punctăm de asemenea faptul că între indicatorul "INVALIDATIONS" și "RELOADS" există o relație de cauzalitate, în sensul că întotdeauna o invalidare va duce, la momentul execuției cursorului, la o operație de RELOAD. Prin urmare, ambii indicatori trebuie să fie luați în considerare înainte de a ne pronunța cu privire la eficiența utilizării "library cache"-ului. Să exemplificăm pe o bază de date izolată, pe care doar eu lucrez:
SQL> create table test (col integer); Table created. SQL> select count(1) from test; COUNT(1) ---------- 0 SQL> select reloads, invalidations from v$librarycache where namespace = 'SQL AREA'; RELOADS INVALIDATIONS ---------- ------------- 84 532După ce am executat interogarea din tabela "TEST" ne așteptăm să avem cursorul corespunzător în "library cache". Haideți să-l invalidăm prin recolectarea de statistici pentru tabelă.
SQL> exec dbms_stats.gather_table_stats(ownname => user, tabname => 'test', no_invalidate => false); PL/SQL procedure successfully completed. SQL> select reloads, invalidations from v$librarycache where namespace = 'SQL AREA'; RELOADS INVALIDATIONS ---------- ------------- 84 533Observați incrementarea numărului de invalidări. Acum să vedem ce se întamplă dacă executăm din nou interogarea din tabela "TEST".
SQL> select count(1) from test; COUNT(1) ---------- 0 SQL> select reloads, invalidations from v$librarycache where namespace = 'SQL AREA'; RELOADS INVALIDATIONS ---------- ------------- 85 533Se poate observa că, urmare a invalidării cursorului, serverul Oracle a fost nevoit să execute o operație de "RELOAD".
Să mai complicăm puțin lucrurile
Am discutat deja despre "hard parse" și "soft parse", punctând diferențele dintre ele. Ei bine, există și un al treilea tip de parsare, pe care maestrul Tom Kyte îl numește "softer soft parse", adică, într-o (semi-)traducere stupidă, "o parsare mai prietenoasă chiar decât o parsare prietenoasă". Despre ce este vorba? Ei bine, Oracle permite memorarea anumitor informații specifice cursoarelor la nivelul sesiunii. Ideea de bază e aceea că, deși am optimizat "library cache"-ul să grupeze cursoarele în acele "bucket"-uri, există în continuare un cost destul de mare în ceea ce privește parcurgerea "library cache"-ului pentru a găsi "bucket"-ul corespunzător și, mai departe, în acesta, cursorul părinte pentru instrucțiunea noastră. Soluția oferită de Oracle este configurarea unui "cache" local sesiunii, care să conțină pointeri spre acele cursoare executate cel mai frecvent de respectiva sesiune. Putem configura această funcționalitate prin intermediul parametrului SESSION_CACHED_CURSORS, parametru care ne spune câte astfel de cursoare să păstrăm la nivelul fiecărei sesiuni.Figura 7 - Pointeri spre cursoare parinte la nivelul sesiunii |
Mai punctăm și faptul că sunt necesare trei executări ale aceleiași instrucțiuni pentru ca Oracle să considere că avem de-a face cu o comandă rulată frecvent, prin urmare benefic a se crea un pointer în "cache"-ul de cursoare specific sesiunii. Managemenul intern al acestei zone de memorie este guvernat de un algoritm de tip LRU (Least Recently Used).
Nu recomand setarea unei valori foarte mari pentru acest parametru. Așa cum am specificat deja, el este specific sesiunii, iar în contextul în care există foarte multe, atunci este posibil să avem nevoie de un spațiu mai mare în "shared pool". Asta sună puțin ciudat, dat fiind că aceste cursoare-pointer sunt stocate la nivelul procesului server în PGA, iar în arhitectura cu procese dedicate, PGA-ul nu are prea mari legături cu "shared pool"-ul. Problema e că toate aceste cursoare-pointer de la nivelul sesiunii sunt urmărite/monitorizate global prin intermediul unor view-uri sistem ce consumă spațiu din "shared pool". Este vorba, în special, despre X$KGLLK care stochează informații despre toate cursoarele deschise, deci inclusiv despre cele din "cache"-ul local de la nivelul sesiunilor, care vor figura ca deschise (open). În plus, căutarea unui cursor în "cache"-ul local se face secvențial, prin urmare, cu cât acesta conține mai multe cursoare, cu atât timpul de căutare va fi mai mare.
În principiu, pentru a stabili corect valoarea parametrului SESSION_CACHED_CURSORS ar trebui să inspectați și valorile următoarelor statistici, fie global din V$SYSSTAT, fie la nivelul sesiunii în V$MYSTAT:
- session cursor cache count
- session cursor cache hits
Notă: În Oracle 11g s-ar părea că este o problemă cu actualizarea indicatorului "session cursor cache hits".
"Library cache"-ul în mediu concurențial
Ce simple ar fi fost lucrurile dacă baza de date ar fi permis conectarea unui singur utilizator și cât de inutil și stupid un astfel de server ar fi fost! Până și așa-numitele baze de date "embedded" au implementate diferite mecanisme tranzacționale (vezi HSQLDB, Oracle Berkeley DB etc.), ce-i drept, poate nu la același nivel de detaliu, dat fiind specificul lor.Un server Oracle puternic tranzacționat este asemeni orașului "New York" la o oră de vârf. Multe mașini și mulți pietoni încercând să-și facă loc pe străzi și trotoare, dar, atenție, cu respectarea unor reguli de circulație bine stabilite. Prin analogie, în contextul Oracle, participanții la trafic (mașini, pietoni) sunt de fapt sesiunile concurente, străzile reprezintă resursele partajate, iar regulile de circulație sunt mecanismele de serializare a accesului ("latch"-uri, "mutex"-uri și "lock"-uri).
Majoritatea celor care dezvoltă aplicații Oracle este familiarizată cu toate aceste concepte de "lock"-ing, dar la nivelul tabelelor și a înregistrărilor lor. Știm cu se mănâncă un "SELECT FOR UPDATE", ce se întâmplă cu actualizările simultane ale aceleiași înregistrări din sesiuni diferite, eventual cum sunt rezolvate situațiile de "dead-lock". Ce este mai puțin evident e că astfel de mecanisme există și la nivelul "library cache"-ului, însă acestea sunt gestionate intern, fără a exista vreun API explicit prin care să le putem controla. Într-un fel, este și normal ca accesul la "library cache" să fie serializat, dat fiind că discutăm până la urmă de o resursă partajată, unde mai multe sesiuni inspectează și modifică simultan această zonă de memorie.
După cum am văzut deja, "library cache"-ul poate fi privit ca pe o structură cu două niveluri. Există, pe de o parte, un nivel al identificatorilor și, pe de altă parte, un nivel al dependințelor. Să exemplificăm! Atunci când discutam de cursoarele asociate instrucțiunilor SQL spuneam că Oracle, înainte de a executa o comandă dată, va căuta mai întâi să vadă dacă nu cumva există deja această instrucțiune în "library cache". Cum face acest lucru? Ei bine, accesând acest prim nivel al "library cache"-ului, cel al identificatorilor. Fiecare astfel de cursor, dar și alte obiecte stocate în "library cache", au un identificator (handler) unic. Cu alte cuvinte, la acest nivel putem privi "library cache"-ul ca pe o colecție de identificatori (sau chei), împrăștiați prin tot library cache-ul. Oricum, aceștia reprezintă doar vârful aisbergului pentru că în spatele lor se ascunde cel de-al doilea nivel de care vorbeam, cel al dependințelor, intra și inter identificatori.
Prin "intra-depenență" ne referim la faptul că fiecare identificator (handler) din "library cache" poate avea asociate sub umbrela sa mai multe componente, la adrese de memorie diferite. Spre exemplu, ne reamintim că un cursor din "library cache" avea ca elemente constitutive planul de execuție, configurarea optimizorului la momentul execuției și date cu privire la variabilele "bind". Analog, pentru un pachet PL/SQL există identificatorul său și, legat de acesta, într-o altă zonă de memorie, codul compilat. Rezultă că avem de-a face cu structuri fragmentare care, deși la nivel conceptual modelează un obiect în sine, reprezentarea internă este compusă din segmente de memorie distincte.
Prin "inter-dependență", ne referim la faptul că un identificator din "library cache" poate să depindă de alți identificatori. Spre exemplu, în cazul unui pachet PL/SQL care conține un apel către o procedură stocată, între identificatorii corespunzători celor două obiecte va exista o relație de dependență, astfel încât, dacă procedura stocată va fi recompilată, stearsă etc., Oracle să poată invalida obiectele dependente. Este interesant că, deși aceste relații de dependență sunt deja consemnate în dicționarul bazei de date, Oracle a decis să implementeze această logică și la nivelul "library cache"-ului.
În acest context, intervin mai multe mecanisme de serializare a accesului concurențial, dintre care două sunt cele mai importante. Avem un prim mecanism denumit "library cache lock", prin care se protejează nivelul identificatorilor și un al doilea, denumit "library cache pin", care protejează nivelul dependințelor. Prin "library cache lock" Oracle se asigură că, odată localizat identificatorul, acesta nu va putea fi afectat de alte sesiuni. Acest mecanism de serializare intervine în special în timpul operației de "parse" și "bind", în acest fel evitându-se ca două sesiuni concurente să poată "parsa" aceeași instrucțiune în același timp. Să exemplificăm prin crearea unei proceduri de dimensiune mare:
create or replace procedure big_proc as i integer := 0; begin i := i + 1; i := i + 1; i := i + 1; i := i + 1; i := i + 1; i := i + 1; i := i + 1; i := i + 1; i := i + 1; i := i + 1; i := i + 1; i := i + 1; i := i + 1; i := i + 1; i := i + 1; i := i + 1; i := i + 1; i := i + 1; i := i + 1; i := i + 1; i := i + 1; i := i + 1; i := i + 1; i := i + 1; i := i + 1; i := i + 1; i := i + 1; i := i + 1; i := i + 1; i := i + 1; i := i + 1; i := i + 1; i := i + 1; i := i + 1; i := i + 1; i := i + 1; ... (linia de mai sus duplicată de 1000 de ori) end; /Dacă rulăm codul de mai sus simultan din două sesiuni distincte, interogând V$SESSION obținem:
SQL> select sid, serial#, status, event, blocking_session 2 from v$session 3 where username='TALEK' 4 and sid not in (select distinct sid from v$mystat); SID SERIAL# STATUS EVENT BLOCKING_SESSION --- ------- ------ ----------------------------- ---------------- 10 20 ACTIVE library cache lock 70 70 21 ACTIVE SQL*Net more data from clientSe poate observa că cea de-a doua sesiune va aștepta într-un eveniment "library cache lock", iar coloana "BLOCKING_SESSION" ne indică care este sesiunea blocantă. Prin urmare, dacă avem de-a face cu vreun utilizator supărat că instrucțiunea sa de "ALTER PACKAGE ... COMPILE" se blochează și nu face nimic, view-ul V$SESSION ar fi primul loc în care ar trebui să aruncăm o ochiadă pentru a identifica sesiunea care blochează.
Notă: În arhitecturile RAC, versiunea 10g, coloanele "BLOCKING_SESSION" și "BLOCKING_INSTANCE" nu sunt
actualizate corespunzător, în cazul în care sesiunea blocantă este pe o instanță, iar sesiunea blocată pe alta. În acest caz, ar trebui
consultat view-ul GV$GES_BLOCKING_ENQUEUE. În 11g situația este remediată.
Mecanismul "library cache lock" este implicat și în invalidarea obiectelor dependente din "library cache". Relația de dependență între obiecte (identificatori) din "library cache" este modelată prin intermediul acestor "library cache lock"-uri. Spre exemplu, de îndată ce un pachet PL/SQL a fost compilat, pentru toate obiectele din "library cache" de care depinde se va plasa un "library cache lock", în modul "shared" pe durata "parse"-ului și, mai departe, în modul "null". Dacă se execută un DDL pe definiția unui obiect dependent, Oracle va invalida obiectele corespunzătoare din "library cache" prin "spargerea" acestor "library cache lock"-uri, în amonte. Documentația Oracle face referire la acest mecanism și sub denumirea "breakable locks". "Breakable" deoarece un DDL nu va fi blocat niciodată de un astfel de "library cache lock".
După ce s-a obținut "library cache lock"-ul, pentru a putea mai departe executa o comandă dată, este nevoie a se construi întreg cursorul, ceea ce înseamnă că trebuie să fie accesate și zonele de memorie care ne dau imaginea completă a respectivului cursor, așa cum ar fi de pildă planul de execuție, variabile "bind" etc., dar și obiectele dependente. Prin urmare, Oracle trebuie să se asigure că aceste componente sunt protejate pentru a nu fi înlăturate din memorie sau modificate de alte sesiuni. Va face toate aceste "jonglerii" prin activarea celui de-al doilea mecanism de serializare, și anume "library cache pin". Un obiect în starea "pin" nu va putea fi înlăturat din memorie ca urmare a acțiunii algoritmului LRU de la nivelul "shared pool"-ului.
Notă: Am evitat să denumesc explicit mecanismele de serializare de mai sus ca "latch"-uri, dat fiind că implementarea lor diferă de la o versiune
la alta. Începând cu versiunea 10g, Oracle a început a reimplementa aceste "latch"-uri sub forma "mutex"-urilor.
Intern, Oracle ține toate aceste informații în trei tabele "magice": X$KGLOB, X$KGLLK și X$KGLPN. Prima tabelă, X$KGLOB, conține obiectele de protejat, X$KGLLK conține "lock"-urile pe nivelul identificatorilor, iar X$KGLPN conține "pin"-urile care protejează inclusiv nivelul dependințelor.
Figura 8 - Relațiile dintre tabelele "magice" ce conțin informații despre "lock"-uri și "pin"-uri în "library cache" |
SQL> create or replace procedure wait_proc as 2 begin 3 dbms_lock.sleep(60); 4 end; 5 / Procedure created. SQL> select distinct sid from v$mystat; SID ---------- 9 SQL> exec wait_proc;Între timp, dintr-o altă sesiune, conectați ca SYSDBA, să vedem ce "pin"-uri au fost puse din sesiunea 9.
SQL> select o.kglnaown, o.kglnaobj, kglobtyd 2 from x$kglpn p, x$kglob o 3 where p.kglpnhdl = o.kglhdadr 4 and kglpnsid = 9; KGLNAOWN KGLNAOBJ KGLOBTYD -------- --------- ------------ SYS STANDARD PACKAGE SYS DBMS_LOCK PACKAGE BODY TALEK WAIT_PROC PROCEDURE SYS DBMS_LOCK PACKAGESe poate observa că, în plus față de procedura "WAIT_PROC", câte un "pin" a fost plasat și pe obiectele de care această procedură depinde.
0 commentarii:
Trimiteți un comentariu