Oracle serializable isolation

Egy Postgresen működő programot portolok Oraclere. A konkurencia kezelés serializable isolation levelre van alapozva. Az Oracle ilyen hibákat csinál:

1) Update nem talál olyan rekordot, amiről biztosan lehet tudni, hogy létezik, mert épp az előbb lett kiírva (insert), még ugyanabban a tranzakcióban. Formailag nincs hibaüzenet, csak éppen az érintett rekordok száma 1 helyett 0, és persze nem hajtódik végre.

2) ORA-01403: no data found
Ez is olyankor jön, amikor nem talál egy éppen előbb kiírt adatot.

3) ORA-08177: can't serialize access for this transaction
Hogy ez mit jelent, az világos, csakhogy olyankor is jön ez a hiba, amikor nincs konkurencia, mert egyedül fut a program.

Ezer tranzakció közül kb. 6-8 romlik így el. Ha a serializable isolation levelt átállítom read comittedre (egyetlen sor módosul), akkor hibátlanul fut. Ebből azt gondolom, hogy valami nincs rendben az Oracle serializable módjával.

Mit lehet tudni erről? Használ valaki serializable isolation levelt, látott hasonlót?

Hozzászólások

Oracle supportot hívnám a kérdéssel.

Ha a felsorolt hibák után rollbackelek, és változatlan adatokkal ismétlem a tanzakciót, akkor az végrehajtódik. Ez természetesen nem megoldás, hanem mutatja a serializable mód defektjét.

--
CCC3

Szia,

bocsánat, ha ordas nagy baromságokat írnék (beteg is vagyok, brü), de azért megosztom a véleményemet:

Először is, szerintem igenis konkurens tranzakcióid futnak.

Másodszor, a serializable isolation-nel ugye azt kéred, hogy a DB annak illúzióját biztosítsa, mintha a tranzakciók egymást kizárva, szigorúan sorosan futottak volna le. A DB ettől még párhuzamosan fogja futtatni a tranzakcióidat, csak commit idején ellenőrzi, hogy ez az illúzió valóban fenntartható-e. Ha nem, akkor kapod a kivételt (lásd még optimistic concurrency violation). Ilyenkor a rollback és az újrapróbálkozás tökéletes módszer (ha nincs nagy versengés); az elpukkant tranzakciót egyszerűen meg kell ismételni a friss DB állapoton.

Ha a read committed-et használod, akkor a kérdéses tranzakciód SQL utasításai más, véglegesített tranzakciók eredményéből fognak kiindulni; ami függőben van, azt nem látják, és valószínűleg COMMIT idején a DB nem törödik a sorosság látszatával.

Megoldási javaslatok:

  • Maradj a szerializált változatnál, és simán próbáld meg újra a tranzakciót. Esetleg valami random backoff-fal :)
  • Használj zárolást (pessimistic concurrency). REPEATABLE READS, ha több SQL utasítás között nem akarod, hogy a független, véglegesített, tranzakciók eredményei megjelenjenek.
  • Vagy pedig READ COMMITTED (szintén zárolás, pessimistic concurrency), de ebben az esetben inkonzisztens végeredmények is születhetnek párhuzamos tranzakciókból, hacsak be nem vezetsz egyedi kulcsokat, és nem teszel kézzel írási zárat mindenre (SELECT FOR UPDATE, ahol csak olvasol). Asszem én ezt használtam "anno"; sajnos nem triviális jól megcsinálni.

Isolation (database systems)

Szóval, ismétlem, nekem az a véleményem, hogy párhuzamos tranzakcióid (session-jeid) futnak, és hogy az Oracle-nek nincs semmi baja, azt teszi, amit kérsz.

Amiről a belinkelt írás szól, az egy ismert jelenség, sok más helyen is le van írva.

Amire én panaszkodok, hogy egy insert utáni update (ugyanabban a serializable tranzakcióban) nem találja a rekordot. Ha a tranzakciót megismétlem, akkor átmegy. Ha az isolation level serializable helyett read committed, akkor elő sem fordul a hiba.

Arra gondolok, hogy ez egyszerűen egy feature az Oracleben. Végül is, ahol ilyen hiba van, ott lehetne "cant serialize transaction" hiba is, akkor viszont úgyis fel kell készíteni az alkalmazást a tranzakció ismétlésére. Csakhogy ez nem mindig elfogadható. Szerintem lesznek olyan esetek, amikor a hibát más okozza, amin az ismételgetés nem segít, az alkalmazás viszont nem tudja, hogy érdemes-e ismételni. Azaz összekutyulódik a hibakezelés.

Az érdekelne, hogy ismer-e valaki komoly Oracle alkalmazást, amiben a konkurencia kezelés a serializable isolation levelre van alapozva. Vagy esetleg van olyan infó, hogy ezt tanácsosabb elkerülni.

--
CCC3

Ha valóban csak egyetlen tranzakciód / session-öd fut, akkor nincs is szükséged a serializable isolation-re; még a READ UNCOMMITTED-et is használhatod, pontosan ugyanaz kell legyen az eredménye. (A saját módosításaidat mindig látod, párhuzamos tranzakció meg nincs.)

Szerintem nem véletlen, hogy a pgsql program a serializable isolation-t választotta; bizonyára van konkurencia. Lehet, hogy a pgsql-nek más a "session ütemezője" vagy mittomén, és ott ezért nincsenek ilyen hibák.

Köszönöm, hogy segíteni próbálsz, de nyomós okom van rá, hogy ezeket a dolgokat firtatom. Korlátozódjunk ennek megválaszolására: Jogos-e, lehetséges-e az, hogy az Oracle egy serializable tranzakcióban egy insertet közvetlenül követő updateben nem találja az előzőleg berakott rekordot?
--
CCC3

Mielőtt megkérdezi valaki, miért kell két lépés (insert+update), miért nem rögtön a végleges rekordot rakom be: Blob beírására egy szokásos technika, hogy először beteszünk egy olyan rekordot, amiben a blob helyén az empty_blob() függvény visszatérési értéke van. Ezt a rekordot egy selecttel visszaolvassuk, amivel megkapjuk a blob handlerét, amivel már írható a blob tartalma. Csakhogy néha elbukik a dolog azon, hogy (serializable módban) az insertet követő select nem találja a rekordot. Erről szól ez a topic. Ez csak egy példa, még számos oka lehet, hogy miért kell insert után update (vagy select), evvel nincs értelme itt foglalkozni.
--
CCC3

Az empty_blob() által generált lokátort az INSERT utasításból azonnal visszanyerheted a RETURNING column_name INTO plsql_var klózzal; ezután az utóbbit használhatod a DBMS_LOB.WRITEAPPEND()-del. UPDATE, SELECT egyáltalán nem kell.


declare
  NEW_ID number(18);
  BLOB_LOC blob;
begin
  insert into MYTABLE (ID, DUMP)
  values (ID_SEQ.nextval, empty_blob())
  returning ID, DUMP into NEW_ID, BLOB_LOC;

  dbms_lob.writeappend(BLOB_LOC, ..., ...);
end;
/

http://download.oracle.com/docs/cd/B19306_01/appdev.102/b14261/returnin…
http://download.oracle.com/docs/cd/B19306_01/appdev.102/b14258/d_lob.ht…

Szerkesztés: a problémába már más is belefutott: http://forums.oracle.com/forums/thread.jspa?threadID=2125045

Nem, most már egyáltalán nem akarok segíteni. Azon túlmenően, hogy eredeti álláspontom tévességét belátva megkerestem az OP-nek azt az OTN szálat, ahol pontosan ezzel a blob-specifikus Oracle bug-gal foglalkozik egy Oracle fejlesztő, illetve azon túlmenően, hogy egy blob-kezelési apróságot megemlítettem, ami esetleg a hiba megkerülésében is segíthet (sajnos nem tudom ellenőrizni, most nem férek Oracle-höz), tényleg nincs értelme ezzel foglalkoznom.

Köszöntem, viszlát.

"However, SERIALIZABLE mode requires applications to check for the "can't serialize access" error, and can significantly reduce throughput in an environment with many concurrent transactions accessing the same data for update. Application logic that checks database consistency must take into account the fact that reads do not block writes in either mode.

Application Tips for Transactions
When a transaction runs in serializable mode, any attempt to change data that was changed by another transaction since the beginning of the serializable transaction causes an error:

ORA-08177: Can't serialize access for this transaction.

When you get this error, roll back the current transaction and execute it again. The transaction gets a new transaction snapshot, and the operation is likely to succeed.

To minimize the performance overhead of rolling back transactions and executing them again, try to put DML statements that might conflict with other concurrent transactions near the beginning of your transaction."

Ez az ora helpből van.

Igen, de az OP-nek az a gondja, hogy neki sosem fut egynél több tranzakciója egyszerre, mégis ilyen kivételt kap. Nem akartam elhinni neki, de miután keresgéltem egy kicsit az oracle fórumain, úgy tűnik, tényleg létezik az a probléma ("Oracle bug"), hogy ha valaki SERIALIZABLE módban BLOB-ot bűvöl, az egyetlen tranzakcióval is belefuthat az ORA-08177-be (ami persze értelmezhetetlen). Valamelyik Ora ügyfél még az SR számát is kiírta a fórumba.


site:forums.oracle.com serializable blob ORA-08177

Lásd ezt a szálat.

Aha, így teljesen érthető már, köszönöm az infót!

Még egy kerülő út: van egy másik megoldás, a createtemporary(blob_var, true) -> write(blob_variable, ..., clob|raw), ami talán használható lenne update helyett.
[blob_variable : lehet table.col(blob) ami rowtype eleme]

megj.:
Úgy hallottam, hogy ezt a dbms_lob pck-t átfaragják a következő verzióban.

Részletesebb megfigyelések.

11g R1 szervert használok, 32 és 64 bites (kétféle) Debian Lennyre telepítve, a két rendszeren ugyanaz az eredmény. A kliens program OCI interfészt használ.

A tesztprogamban 500 tranzakció van, egy tranzakcióban kb. 20 insert, összesen kb. 10 ezer insert. A rekordoknak egyedi kulcsuk van. Az insert utasítás minden esetben hibaüzenet nélkül fut, és a _tapasztalat_ azt mutatja, hogy végre is hajtódik. Végrehajtódik, értsd: ha az insert után közvetlenül commitolnám a tranzakciót, akkor a rekord benn volna a táblában. A program egyedül fut, nincsenek konkurens tranzakciók.

A kísérlet:

1) Serializable mód. Közvetlenül az insert után select-tel visszaolvasom az éppen kiírt rekordot. Az esetek 0.5 százalékában (10 ezerből 50 eset) a select nem találja az éppen előbb berakott rekordot. Ha az insert után egy kicsit várok (sleep) vagy (tranzakción belül) az insertet vagy a selectet ismétlem, az hatástalan. Ha a tranzakciót abortálom, majd az egész tranzakciót megismétlem, akkor az lefut (és az insert utáni select talál).

2) Read committed mód. Ugyanaz a program, mint az előbb, csak az első utasítás nem serializable, hanem read committed módot állít be a sessionre. Nem jelentkezik hasonló hiba, az insert utáni select minden esetben talál.

Az a kérdés, hogy ezt a jelenséget bele lehet-e erőltetni a specifikációba (mármint elfogadni jogosnak), vagy hibának kell tekinteni. Workaround mindenesetre elképzelhető: Minden insert után visszaolvasó select, és ha az nem talál, akkor "cant serialize" error dobás. Az alkalmazás nyilván fel van készítve a "cant serialize" kivétel kezelésére (ami általában a tranzakció ismétlése).

Miért nem lehet elfogadni, hogy az insert berakta a rekordot? Kész-passz, nem olvasom vissza, minek is tenném, hiszen ismerem a tartalmát. Lehet, hogy furcsa a visszaolvasás, de az SQL specifikáció szerint nem tilos. Az SQL specifikáció szerint a select látja a saját tranzakcióban kiírt rekordot, még ha az nincs is commitolva. Ha a select nem látja a rekordot, akkor az SQL _specifikáció_ szerint a rekord nem is létezik.

Most képzeljük, hogy egy kétlábas könyvelési tranzakciót programozunk, a második lábat írjuk éppen kifele. Ellenőrzési mániában szenvedünk, és ezért egy selecttel visszaolvassuk a kiírt rekordot, ám a select nem talál. Abban a helyzetben vagyunk, hogy a _tapasztalat_ alapján úgy gondolhatjuk, hogy a rekord kiíródott, az SQL _specifikáció_ alapján azonban úgy kell gondolnunk, hogy a rekord nem íródott ki. Kell-e abortálni? Hint: A tét (a könyvelési tétel összege) 100 millió.

--
CCC3

szerintem a sajat sikeres inserted minden esetben latnod kellene, kiveve, ha egy masik read uncommited-ben futo tranzakcio kitorolte az inserted utan de a selected elott a rekordot.
de lehet hogy meg ez is csak akkor fordulhat elo, ha repetable reads-nel "lazabb" izolacios szinten vagy az insertes tranzakciodban.
szoval nekem mint oracle-hoz nem erto embernek furcsan hangzik, hogy a sajat insertemet nem latnam egy tranzakcioban.

Tyrael

Az a kérdés, hogy ezt a jelenséget bele lehet-e erőltetni a specifikációba (mármint elfogadni jogosnak), vagy hibának kell tekinteni.

Szerintem sokkal hasznosabb, ha gyakorlatiasabb megközelítésből nézed a dolgot:
- ha hibának kell tekinteni, és
- az Oracle is elfogadja ezt hibának,
akkor
lesz-e még az életben fix a hibára, vagy előbb öregszel és halsz meg, semmint fix készüljön a rá?

Én nem tennék rá nagy téteket, hogy valaha elkészül a fix.

A személyes, ~10 éves Oracle adatbázis tapasztalatom az, hogy a LOB-okat akkora ívben kell kerülni, amekkorában csak lehet.

Ez egy nagyon jó példa arra, hogyan működik elméletben ill.
gyakorlatban valami, nem beszélve arról hogyan implementálták.

A read commited statment alapú, a serializable pedig
tranzakció szintű, ez utóbbi az "egész" adatbázis egy állapotát
veszi figyelembe és ezt látjuk a tranzakcióban(elvágtuk egy idővonallal). Ez jó is lenne,
de sok buktatóval is jár. Pl. itt van mindjárt a rollback
segmens, amiben a tárolt adatok nem őrződnek meg a végtelnségig,
hanem egyes tranzakciók ezt felül is írhatják(insert,.., rollback, commit) és
mindjárt olyan adattal dolgoznál, ami nincs is ott(ellenben a read committed-el aminél ez kevésbé jön elő)

"Az a kérdés, hogy ezt a jelenséget bele lehet-e erőltetni a specifikációba (mármint elfogadni jogosnak), vagy hibának kell tekinteni."
Elméletben ilyen nem fordulhat elő, de gyakorlati szinten igen.

megj.: használj read commited beállítást(default)(kevesebb álmatlan éjszaka, kevesebb javító update script)

Számos megoldás van az érzékeny adatok konzisztens kezelésére:
1. lock (mondjuk fejrekordon select col1, col2, ..from table for update)
2. éjszakai batch program végigszalad a teendőkön
3. dolgozd fel temp táblában->ellenőrizd->és egyetlen mozdulattal tegyed át a munka táblába, amit csak olvashatnak(+constraint)

Hupos reggel nem rendelkező kollégám véleménye:

Nézegetve a doksit, meg amit a srác írt előszőr valami PL/SQL gondra gyanakodtam amivel én is szívtam korábban. De mivel ők OCI-ban küzdenek így ez kilőve.

Viszont minden olyan táblára amire serializable isolation level –ben tranzakciókat csinálnak az INITRANS paraméterét legalább 3-ra kell állítani.

>>Oracle stores control information in each data block to manage access by concurrent transactions. Therefore, if you set the transaction isolation level to serializable, then you must use the ALTERTABLE command to set INITRANS to at least 3. This parameter causes Oracle to allocate sufficient storage in each block to record the history of recent transactions that accessed the block. Higher values should be used for tables that will undergo many transactions updating the same blocks.<<

Ez még mondjuk a 9-es Oracle, de gondolom iránymutatásnak megfelel. Bár én egyből 100-ra alterálnék, az olyan szép szám :)

Viszont mivel ők 11-t használnak még egy csomó dolog bekavarhat, de indulásnak talán elég ennyi

Ja még az is egy jó tanács: azt a tranzakciót amivel néha baj van sima SQL-ben az SQLPLUS-al is tesztelgessék, hátha még sem az oracle szerver a szar, hanem az OCI-t nem trivi esetleg programozni kliens oldalon :)

lacos kollega adott egy linket, amiben írva vagyon a hiba:
http://hup.hu/node/100133#comment-1237376
--
>>... to manage access by concurrent transactions->tehát akkor
1 tranzakció esetén is legyen legalább 3?

szerk.: tárolt eljárást akartam írni még délelőtt, de nem jutottam
odáig(így is jelentkezik-e a hiba?)

"you must use the ALTERTABLE command to set INITRANS to at least 3"

Erről nem tudtam, ki fogom próbálni.

"az OCI-t nem trivi esetleg programozni kliens oldalon"

Ha nem is tudom matematikai szigorúsággal bizonyítani, hogy a tesztprogram hibátlan, mégis elég meggyőzően lehet érvelni mellette. Ugyanaz a program (egy utasítás különbséggel) read committed módban jó. Ugyanaz a program, ugyanazokkal az adatokkal a megismételt tranzakciót helyesen hajtja végre. Nehezen képzelhető el, hogy ez véletlen.

--
CCC3

Kipróbáltam tárolt eljárásban, nálam nem volt hiba.


create table TST_BLOB
(
  ID   NUMBER not null,
  MIEZ BLOB
)
tablespace valami_data
  pctfree 10
  initrans 1
  maxtrans 255
  storage
  (
    initial 64K
    minextents 1
    maxextents unlimited
  );

create or replace package seri_blob is

  FUNCTION F_GENEREAL( P_HOWMANY NUMBER )
   RETURN NUMBER;
   
end seri_blob;
/
create or replace package body seri_blob is
 
  FUNCTION F_BEIR( P_TST_BLOB IN OUT NOCOPY TST_BLOB%ROWTYPE )
   RETURN NUMBER 
   IS
   BEGIN
     INSERT INTO TST_BLOB VALUES P_TST_BLOB;
     RETURN 0;
  END F_BEIR;
  
  --====================================================
  
  FUNCTION F_BLOB_PREPARE( P_SIZE NUMBER,
                           P_RESULT IN OUT NOCOPY TST_BLOB%ROWTYPE )
   RETURN NUMBER
   IS
     LRAW_X        RAW(8000);
     LN_ASC        NUMBER;
     I             NUMBER DEFAULT 1;
     L_ST          VARCHAR2(8000);
     LS_OUT        VARCHAR2(10) DEFAULT 'EZKERULBE';
     L_SZOVEG      VARCHAR2(8000);
   BEGIN
     WHILE I <= FLOOR(P_SIZE / LENGTH(LS_OUT)) LOOP
       L_ST := L_ST || LS_OUT ;
       I := I +1;
     END LOOP;
     LRAW_X := UTL_RAW.CAST_TO_RAW(L_ST);
     LN_ASC := LENGTH(L_ST);
     DBMS_LOB.CREATETEMPORARY(P_RESULT.MIEZ, TRUE);
     DBMS_LOB.WRITE(P_RESULT.MIEZ, LN_ASC, 1, LRAW_X);
     RETURN 0;
  END F_BLOB_PREPARE;
  
  --====================================================
  
  FUNCTION F_GENEREAL( P_HOWMANY NUMBER )
   RETURN NUMBER
   IS 
     L_ERROR_DB   NUMBER DEFAULT 0;
     L_TST_BLOB   TST_BLOB%ROWTYPE;
     L_DB         NUMBER DEFAULT 1;
     L_VAN        NUMBER;
     L_RET        NUMBER;
     SERIAL_HIBA  EXCEPTION;
     PRAGMA EXCEPTION_INIT(SERIAL_HIBA, -8177 );

   BEGIN
     COMMIT;
     SET TRANSACTION ISOLATION LEVEL SERIALIZABLE NAME 'LEAKADE'; 
     
     L_RET := F_BLOB_PREPARE( P_SIZE => 1000,
                              P_RESULT => L_TST_BLOB );
     WHILE L_DB <= P_HOWMANY LOOP
        BEGIN
          L_TST_BLOB.ID := L_DB;
          L_RET := F_BEIR( P_TST_BLOB => L_TST_BLOB );
          SELECT 1 INTO L_VAN
            FROM TST_BLOB
            WHERE ID = L_DB
          ;
        EXCEPTION WHEN SERIAL_HIBA THEN
          L_ERROR_DB := L_ERROR_DB +1;
          DBMS_OUTPUT.PUT_LINE(L_DB || ' ' ||SQLERRM);
          WHEN OTHERS THEN 
           DBMS_OUTPUT.PUT_LINE(L_DB || ' EGYEB ' ||SQLERRM);
        END;        
        L_DB := L_DB + 1;
     END LOOP;                              
     DBMS_LOB.FREETEMPORARY(L_TST_BLOB.MIEZ);
                              
     RETURN L_ERROR_DB;
  END F_GENEREAL;

  --====================================================  
  
end seri_blob;
/

így teszteltem:


DECLARE
  L_RET NUMBER;
BEGIN
  L_RET := seri_blob.f_genereal(p_howmany => 100 );
END;

adtam neki 10000-et is, akkor sem láttam hibát.

Kérdés, hogy a tárolt eljárás interpretálása a szerveren hogyan viszonyul egy OCI sql statement végrehajtásához. Lehetnek közbülső szintek, amik elfedhetnek ezt-azt. Azt sem tudom, hogy az OCI a legalacsonyabb szintű interfész-e, vagy más frontendek esetleg másra (az OCI-t elkerülve) építkeznek.

Nálam a tranzakciók bonyolultabbak. Nem csak egy táblába pakkolok, hanem van 10-15 tábla, egy tranzakcióban pedig átlagosan 20 insert és update. Vagyis jobban dolgoztatom a szervert.

Az eset amit leírtam nem kizárólag a bloboknál fordul elő. Először a bloboknál vettem észre, de aztán kiderült, hogy teljesen közönséges insert-select vagy insert-update páros is kihozza (nálam).

--
CCC3

Az egyik fent fut, a másik közvetett módon(oci). Azért küldtem el
a forrást, hogyha van időd nézd meg, hogy nálad jelentkezik-e a hiba,
és csak a példa miatt blob-os.
Ez egyáltalán nem jelenti azt, hogy ez megoldás a serializable
gondokra, sőt...több tranzakciónál meg sem találod az okok nagyrészét. Mindenképpen ajánlom a tárolt eljárást, ahol csak lehet(pl. nem kell klienst fordítani, ha a pck-ban ki lehet javítani).
Próbáld csökkenti a műveletek számát, 1000->800...akkor is jelentkeznek-e a hibák?
--
Használhatsz timestamp-ot is, ha nagyon érzékeny az app. a konzisztenciára.

Még egy apróság
innen
"According to the manual, it converts every ‘select’ to a ‘select for update’ in ‘serializable’ mode:"

itt az ember azt mondja, hogy egy lehetséges módja a hibás működés
elkerülésére, ha mindegyik selectet for update(nowait)-el kiegészít.
Azért az szép történet...

Megírtam a programod megfelelőjét CCC-ben. Ez alatt a program alatt egy olyan könyvtár van, amelyik OCI interfészen keresztül SQL utasításokat küld a szervernek. A küldött SQL utasításokat kilistáztam és elláttam néhány megjegyzéssel. Sajnos a fetch utasítások nem látszanak a listában.

--
CCC3

Hát ez szomorú.
Lehet, hogy az oci itt a hunyó?
Ez viszont ellentmondás, mert batch-ben futott valami és ott is jelentkezett a seri hiba, pedig egymagában volt a session.
--
Esetleg még megnézhetnéd, hogy ha a tárolt eljárást hívod,
előjön-e a hiba?
(
csak 1 fv. hivás és leteszi az összes rekordot, vagy
több fv. hívás és egyesével megy a rekord(persze itt megugrik a háló forgalma)
)
Initrans sem játszik ezek szerint(1 session-t alapul véve; nálad mennyi
a maxtrans értéke?)

maxtrans=255 (default)

Nem a 8177 can't serialize hibára kell koncentrálni. El tudom fogadni, ha az Oracle néha ilyet dob. Ugyanis, ha egy alkalmazás serializable levelt használ, akkor fel van készítve az ilyen hibák kezelésére, és szükség esetén rollback után megismétli a tanzakciót. Tehát, ha a szerver (akár indokolatlanul) 8177 errort dob, akkor legfeljebb néhány tranzakció duplán fut. Nem különösebben érdekes.

Súlyosabb dologról van szó: insert után a select vagy update néha nem talál. Erre eddig azt a workaroundot találtam, hogy az insert után azonnal visszaolvasom a rekordot, és ha a visszaolvasás nem talál, akkor 8177 errort dobok, és lásd mint előbb. Így jön ide a can't serialize error. Jelenleg ez nekem bele van építve a magasabb szintű insert-be (a row objektum insert metódusába).

Példa nem triviális insert utáni selectre: Tegyük fel, hogy van egy árfolyam táblám, ahonnan néha előveszek egy árfolyamot. Ha a keresett árfolyam még nincs meg a táblában, akkor röptében elindítok egy árfolyam megszerző függvényt, ami email üzenetváltással, xmlrpc hívással, akárhogyan megszerzi az árfolyamot és beteszi az előbbi táblába, hogy a későbbi előfordulásoknál már meglegyen. Utána újra megpróbálom kiolvasni az árfolyamot.

--
CCC3

Ez a "nem triviális" eset kezelése: rövid tranzakciók kellenek,
ha csak áll a session az nem gond. Előzetes vizsgálat az adatok
létezésére.

"Nem a 8177 can't serialize hibára kell koncentrálni."
A kettő nagyon összefügg, gondolj pl. a read committed szintre, ahol nincs
ilyen gond.

Szintén kollégámtól, hátha:

Ez azt hiszem pont az érintett kérdés:

http://forums.oracle.com/forums/thread.jspa?threadID=593201

Szóval ha eldobják az összes indexet :), akkor tuti megy.
A hiba tahát az, hogy az indexet átrendeződik, mivel az adott block betelt és erről a tranzakció nem értesül ebben az izolációs szintben.
Tehát ezért van ez a “véletlenszerűség”.
Bár azt írják, hogy ez már javított bug, de ki tudja, meg csacsognak némi memory configról :)

Bár azt írják, hogy ez már javított bug, de ki tudja, meg csacsognak némi memory configról :)

A bug státuszmezőjében nem a 'kijavítva' szerepel, ergó szerintem ezt nem fixálták.

Viszont nagyon sok paramétertől függ, hogy előjön-e, ezért lehetett, hogy a fórumos usernek a 10g-ra váltás után nem jött elő.

Van az OCI-ban valami megfelelője az insert ... returning-nek? Szerintem nincs. Azonkívül nem csak a bloboknál fordul elő, hogy insert után select vagy update kell. Az alkalmazás megköveteli, hogy a táblának egyedi kulcsa legyen, ami egyenértékű egy unique indexszel. Úgyhogy ezek a kerülőutak nem járhatók.
--
CCC3

Összeraktam egy kis csomagot, amiben az sqlplus állítja elő ugyanazt a hibát, amiről itt szó volt (insert utáni select nem találja az éppen előbb berakott rekordot). Ez bizonyítja, hogy a hiba nem a kliens oldalon van. A projekt itt található. A projektet a futási eredményekkel együtt csomagoltam össze, hogy CCC nélkül is vizsgálható legyen.
--
CCC3

Ha ezt reprodukálni tudod úgy, hogy ne select legyen a második, hanem egy másik insert, ami hivatkozik idegen kulcsként az első insert sorára (azaz insert + insert legyen a két parancs), és úgy is hasra esik, akkor nem lehetetlen, hogy kiverhető a kedves vendorból egy fix - ebben az esetben ugyanis nem workaroundolható a probléma. A selectes visszaolvasásra van workaround: insert into ... returning.

Érzékletesébbé teszem a problémát. Van egy sqlplus scriptem, amit programmal csináltam, és így néz ki:


 drop table proba;
 create table proba( kulcs varchar(24) not null, megjegyzes varchar(128) );
 create unique index proba_primarykey on proba (kulcs);
 alter session set isolation_level=SERIALIZABLE;

 insert into proba (kulcs,megjegyzes) values ('11:39:08!00000001','11:39:08');
 update proba set megjegyzes='!!!!' where kulcs='11:39:08!00000001';
 commit;

 insert into proba (kulcs,megjegyzes) values ('11:39:08!00000002','11:39:08');
 update proba set megjegyzes='!!!!' where kulcs='11:39:08!00000002';
 commit;

 ...

 insert into proba (kulcs,megjegyzes) values ('11:39:11!00000286','11:39:11');
 update proba set megjegyzes='!!!!' where kulcs='11:39:11!00000286';
 commit;
quit

Vagyis insert, update, commit parancsok ismétlődnek 286-szor. Az update mindig az utoljára insertált rekordot módosítja. Az eredmény:


SQL*Plus: Release 11.2.0.1.0 Production on Mon Mar 21 11:45:13 2011
Copyright (c) 1982, 2009, Oracle.  All rights reserved.
Connected to:
Oracle Database 11g Release 11.1.0.6.0 - 64bit Production
Table dropped.
Table created.
Index created.
Session altered.

1 row created.
1 row updated.
Commit complete.

1 row created.
1 row updated.
Commit complete.

Ilyenek ismétlődnek 285-ször. És 286-dikra jön a meglepetés.


1 row created.
0 rows updated.
Commit complete.

Disconnected from Oracle Database 11g Release 11.1.0.6.0 - 64bit Production

Vagyis az utolsó update nem hajtódik végre.

Ha a script elejéről kiveszem az indexet vagy nem állítom be a serializable isolation levelt, akkor a hiba megszűnik. A hiba determinisztikus, és 3 féle szerveren is konzisztensen jelentkezik: 11gR1(32bit), 11gR1(64bit), 11gR2(64bit). A hiba függ attól, hogy milyen hosszú a kulcs. Más hosszakkal máshol jön elő a hiba. Ahol az update hibázik ott a select sem működik. Szóval ki van ez próbálva sok variációban. Mit mondjunk akkor a király ruházatáról.

Ha valaki ki akarja próbálni, felraktam a scriptet ide.

--
CCC3

Ezzel kéne megpróbálni. Ha megy a reprodukálás, akkor szerintem simán bejelenthető a supporton. Persze előtte ki kéne próbálni megemelt initrans-szal is.

Mellesleg nekem 10.2.0.3-n 32KB-s blokkmérettel nem jelentkezik a probléma még 10000 rekordnál se.


drop table p2;
drop table proba;

create table proba( kulcs varchar(24) not null primary key,
megjegyzes varchar(128) );
create table p2 ( kulcs varchar(24) not null
references proba(kulcs), megjegyzes2 varchar(128) );
create index p2_kulcs on p2 (kulcs);

alter session set isolation_level=SERIALIZABLE;

insert into proba (kulcs,megjegyzes) values ('abcdefghijkl/00000001', 'abcdefghijkl/00000001');
insert into p2 (kulcs, megjegyzes2) values ('abcdefghijkl/00000001', 'abcdefghijkl/xx');
commit;
insert into proba (kulcs,megjegyzes) values ('abcdefghijkl/00000002', 'abcdefghijkl/00000002');
insert into p2 (kulcs, megjegyzes2) values ('abcdefghijkl/00000002', 'abcdefghijkl/xx');
commit;

Ez itt olyan, mint egy matematikai ellenpélda. Egy tétel érvénytelenségét lehet úgy is bizonyítani, hogy konstruálunk egy ellenpéldát. Egy esetet, ahol a tétel feltételei teljesülnek, de az állítás nem. Most a tétel feltételei azt jelentik, hogy tisztán Oracle eszközöket és szabályos SQL utasításokat használunk. Az állítás nem teljesülése azt jelenti, hogy az Oracle hibázik. Tehát megdől a tétel, hogy az Oracle hibátlan.

Egy ellenpélda akkor jó, ha minél egyszerűbb. Az én (ellen)példám nagyon egyszerű, mert kizárólag az SQL legegyszerűbb alapműveleteit tartalmazza (insert, update), egyetlenegy táblával állítható elő, és az az egy tábla is a lehető legegyszerűbb. Ha még egyszerűbb volna, akkor viszont már nem jönne elő a hiba. A példában alkalmazott tranzakciók is a lehető legegyszerűbbek, csak két műveletet fognak össze. Ha minden művelet külön commitolva volna, megint csak nem jönne elő a hiba. Ha nem teszek indexet a táblára, vagy nem írom elő az index unique minősítését, nem jön elő a hiba. Ha nem állítom, be a serializable módot nem jön elő a hiba.

Ha a hiba mindig az első rekordnál előjönne, akkor már rég észrevették és kijavították volna. Ennél trükkösebb a helyzet, a 286. rekord romlik el. Az általam elérhető összes szerver hibázik a 286-dik rekordnál. Ha másmilyen adatokat rakok a kulcs mezőbe (hosszabbat, rövidebbet), akkor ez a 286-os szám megváltozhat, de akkor az új szám lesz állandó a különféle szervereken.

Folytatva az egyszerűséggel: Az egyszerű ellenpéldának az a haszna, hogy erősebb állítás is megfogalmazható, mint hogy az Oracle nem hibátlan (mert ugye, ki látott már hibátlan szoftvert). Ez az erősebb állítás, hogy az Oracle az SQL olyan alapműveleteit sem tudja minden esetben megfelelően végrehajtani, mint az insert és update, még akkor sem, ha ezek a műveletek kizárólag egy táblát érintenek, és azon az egy táblán nincs előírva semmilyen ellenőrzés*, triggerek meg effélék. Ez így azért már súlyos, alig hiszem el.

--
CCC3

*Pontosabban, egy ellenőrzés mégis csak elő van írva: A unique index kényszeríti ki a kulcs egyediségét.

Oracle 10g XE 32bit, Win7 32bit "szerver", default minden beállítás, előjött ugyanez.
Több insertnél, a 288.-nál is előjött.
Csináltam egy olyan változatot, aminek egy commit van a legvégén, ott a 286.-tól nem tud commitolni.

Ha átírom az egészet úgy, hogy begin-end blokkban legyen, és alter session helyett a set transaction isolation level SERIALIZABLE; paranccsal állítok, akkor:

Egy commit a végén verzió:
https://gist.github.com/880583/8f3a1cc3cd29b6cdfbdd529ab3220021216b72e9
Eredmény: 286. sortól elromlik

Minden UPDATE után külön commit:
https://gist.github.com/880583/da0d5c0dea221009d792fdf665ce100b81ca98e0
Eredmény: mind jó. Nem jó. Mert ugye mind külön tranzakció.

Itt az a verzió, ami ugyanúgy működik, mint a tied, de begin-end-es:
https://gist.github.com/880583/b08b80c563c52675dd4f3ac9f9e9dce3b664624b
Ez elszáll
ORA-08177: can't serialize access for this transaction
ORA-06512: at line 866
kivétellel.

ROWDEPENDENCIES használata elvileg (gyakorlatilag nem ritkult nálam) ritkítja az ilyen eseteket:

create table proba( kulcs varchar(24) not null, megjegyzes varchar(128) ) initrans 10 rowdependencies pctfree 5;

http://stackoverflow.com/questions/2326750/randomly-getting-ora-08177-w… illetve http://deniskrjuchkov.blogspot.com/2010/02/oracle-challenge.html itt is ezzel küzdöttek. (megoldás itt sincs)

A linkelt kódomban át van írva begin-endre, és ki van kommentezve az alter session...

Ehelyett van a begin-end-en belül set transaction isolation level SERIALIZABLE; ami csak az adott tranzakcióra vonatkozik, de elrontottam, mert csak az első tranzakcióra érvényes. A másodiknál már visszaáll read committed-re magától.

Ezért rossz az első hozzászólásomban az áthúzottal írt linken a kód.

http://stackoverflow.com/questions/2326750/randomly-getting-ora-08177-w… illetve http://deniskrjuchkov.blogspot.com/2010/02/oracle-challenge.html itt is ezzel küzdöttek. (megoldás itt sincs)

Az első link alatt van egy ilyen hozzászólás:

Shortly, sometimes during index restructurization undo data becomes unavailable. Any transaction that runs at serializable isolation level and requests the data that is somehow related with this index will get ORA-08177. This is a half-bug half-feature of Oracle.

Ebben a rész-szálban amúgy is kiderítették itt a hupperek, hogy ha nincs index, akkor nincs probléma. Javaslat (workaround):

  1. Az elején ne hozzunk létre indexet,
  2. Feltöltés közben az INSERT ... RETURNING INTO-t használjuk az UROWID visszakérésére,
  3. Minden sorra a további select/update-nél az UROWID-t használjuk a WHERE feltételben, esetleg úgy, hogy mellette még megköveteljük AND-del az éppen beszúrt kulcsértéket is. Az UROWID miatt az access path ROWID alapú lesz, vagyis villámgyors (O(1)), a PK érték pedig csak egy utólagos biztonsági szűrőként fog érvényesülni.
  4. A végén tegyük rá az indexet.

Volt olyan kérdés, hogy OCI-ból lehet-e INSERT ... RETURNING INTO-t használni. Igen, lehet:

http://download.oracle.com/docs/cd/B10500_01/appdev.920/a96584/oci05bnd…

(Egyébként érdekesség, hogy az INSERT ... RETURNING INTO-t még dinamikus SQL-lel is lehet használni, ahol a kimeneti PL/SQL változók rendesen bind-olhatók.)

Ezek jók, kivéve, hogy az indexet nem lehet elhagyni, mert kell az egyediség kikényszerítéséhez. A primary key ugyanaz. A unique index helyett primary key ugyanúgy produkálja a hibát.

Most készülök feltúrni az OCI dokumentációt, hogy hogyan lehet megszerezni insert után a rowid-t.

--
CCC3

Tehát akkor itt az index körül vannak a gondok.


declare
 lr_fej   table1%rowtype;
 l_rowid  rowid;
begin
  lr_fej.unid := 1;
  insert into table1 values lr_fej return rowid into l_rowid;
  dbms_output.put_line(l_rowid);
end ;

Függvényeket tudsz hívni ccc-ből?

szerk>lacos kollega megtalálta a returning eset leírását

There was a long-standing bug in Oracle serializable isolation,
bug #440317, where if an insert causes an index block to split, any
subsequent search for this new row in the same tx uses the previous
unsplit index page (for read consistency) which does not have the new
row! I remember dealing with this in the 1990's, and I would be surprised
that it is not fixed yet. Can you try Oracle 10g?

You would also need to configure your per-table memory to store all the
data in that huge test transaction.

bár csodálkoztam, hogy ez nincs még javítva, de a tünetek alapján pontosan illik a jelenlegi szituációra.

Tyrael

Pontosan. Abból gondolom én is ezt, hogy a kulcshossz változásával változik az első hiba bekövetkezésének helye. A példában a 286. rekord, de ahogy írtam, más kulcshosszaknál másik rekord romlik el. A split-re pedig mikor kerül sor? Amikor betelik egy index page. Hogy mikor telik be, az pedig éppen a kulcshossztól függ.
--
CCC3

Most borzasztó gusztustalant fogok mondani, de amíg nem javítják a hibát, addig ebből is kisülhet valami: mivel csak egy session fut bármely pillanatban, azért elég lehetne egy nem egyedi index is (a probléma ugye csak unique index esetén lép fel), és az egyediség kézi ellenőrzése. A nem egyedi index ezt a manuális ellenőrzést gyorsítaná fel.

... Legalábbis akkor, ha DML csak az alkalmazáson keresztül éri az adatbázist (kb. "trusted procedure").

1. Miért gondoljuk, hogy csak a unique indexeket érinti a probléma? Nekem ez elsőre nem világos.

2. Nem tudom, hogy más hogy van vele, de OLTP-nél referential integrity nélkül élni elég értelmetlenné teszi az Oracle használatát, az meg nemigen van meg primary key nélkül. Akkor ennyi erővel már sqlite is lehetne, az legalább pici és kiba gyors (KISS)...

Ezeket lehet helyettesíteni triggerben, de lassabb


before insert..
begin
select 1 from table where uniquenak_kene_lennie_col =  :new.uniquenak_kene_lennie_col
  return már van
exception no_data_found then
  return ok
end;

unique esetén:
Van-e külön táblatér az index-eknek? Esetleg úgy tesztelni,
hogy a storage másik táblatérben legyen, talán storage-el
lehetne még játszani
(
initial 2M ->1M ->500k...
next 1
minextents 1
bár ha határon van, akkor ennek átlépése előidézheti a fenti hibát..

Összefoglalom, mi szűrődött le eddig.
Olyan kísérletekről van szó, ahol be van állítva a SERIALIZABLE isolation level.
Kétféle hiba van.

1) ORA-08177: can't serialize access for this transaction

Ezzel az a probléma, hogy olyankor is előjön, amikor biztosan tudható, hogy nincsenek konkurens tranzakciók. A jelenség alkalmazásfejlesztői szempontból azonban nem veszélyes. Ha ugyanis egy alkalmazás serializable isolation levelre alapozza a konkurencia kezelést, akkor fel kell legyen készítve az ilyen hibák kezelésére. Ez abból áll, hogy a hibát elkapjuk, rollbackelünk, és megismételjük a tranzakciót. Mondjuk 1000-ből egyet meg kell ismételni. Szót sem érdemel.

Megfigyelésem szerint a hiba véletlenszerűen jelentkezik. Valójában nem véletlenszerű, csak feltehetően függ a szerver egész előtörténetétől.

2) ORA-01403: no data found

Az insert utáni select hibázik így. Az update feltehetően ugyanúgy, ugyanazért nem találja a rekordot, mint a select, csak a hibaüzenet az update esetében "0 rows updated".

A hibára jellemző, hogy nem függ az szerver előtörténetétől, csak az adott tábla állapotától. Ugyanis, ha mindig drop table-val indítjuk a tesztet, akkor a hiba mindig ugyanott következik be. Ezen belül is úgy gondolom, hogy az index állapota számít. Ugyanis a hiba csak akkor jön elő, ha a táblán unique index van. Ha nincs index, vagy van index, de nem unique, akkor nem jön elő a hiba. A unique index lényege, hogy kikényszeríti a kulcs egyediségét. (Megj: unique index helyett primary key ugyanúgy produkálja a hibát.)

Megfigyelésem szerint a 01403-as hibán nem segít a várakozás és a select/update művelet ismétlése. Ami segít, az az egész tranzakció abortálása és ismétlése.

Alkalmazásfejlesztői szempontból ez nehezebb eset, mert az alkalmazás nem mindig tudja, hogy az Oracle hibája miatt fordult-e elő a jelenség, vagy tényleg nincs benne a sor a táblában.

--
CCC3