Ovo poglavlje je posvećeno dizajnu aplikacija koje koriste baze podataka, a za koje se očekuje da će biti izvršavane u višekorisničkom okruženju. Ovo podrazumeva da je potrebno uvesti dodatne mere u slučaju da više aplikacija želi da pristupi nekom objektu u bazi podataka (na primer, određenom redu ili tabeli).
Svi SQL-aplikativni programi se izvršavaju kao deo aplikacionih procesa ili agenata. Aplikacioni proces podrazumeva izvršavanje jednog ili više programa, i predstavlja jedinicu kojoj SUBP alocira resurse i katance. Različiti aplikacioni procesi mogu da povlače sa sobom izvršavanje različitih programa ili različita izvršavanja istog programa.
U višekorisničkom okruženju, osnovna pretpostavka je da više od jednog aplikacionog procesa može da zahteva pristup istim podacima u isto vreme. U tu svrhu, da bi se obezbedio integritet podataka, potrebno je implementirati i koristiti mehanizam poznat pod nazivom zaključavanje.
Zaključavanje (engl. locking) predstavlja mehanizam zasnovan na katancima koji se koristi za održavanje integriteta podataka pod uslovima višekorisničkog okruženja.
Korišćenjem zaključavanja se može sprečiti da, na primer, dva aplikaciona procesa izvrše ažuriranje istog reda u isto vreme.
SUBP upravlja katancima da bi onemogućio da nepotvrđene izmene napravljene od strane jednog aplikacionog procesa budu vidljive od bilo kog drugog procesa. U trenutku kada se neki proces završi, tada se svi katanci koje je proces dobio od strane RSUBP, i držao ih, oslobađaju. Ipak, aplikacioni proces može da eksplicitno zahteva da se neki katanac oslobodi ranije. Ovo je omogućeno korišćenjem operacije potvrđivanja izmena, koja oslobađa katance koji su dobijeni tokom jedinice posla i koja, takođe, potvrđuje sve izmene koje su obavljene tokom te jedinice posla.
Direktni pozivi DB2 funkcija i ugnežđeni SQL omogućavaju režim konekcije koji se naziva konkurentne transakcije (engl. concurrent transactions) kojim se omogućavaju višestruke konekcije ka bazama podataka, pri čemu svaka od njih predstavlja nezavisnu transakciju.
Aplikacija može da ima više konkurentnih konekcija ka istoj bazi podataka.
Kao što smo rekli, katanci predstavljaju osnovni alat za uspostavljanje mehanizma zaključavanja. Svaki katanac ima odgovarajuće karakteristike:
Režim (engl. mode) definiše tip pristupa koji je dozvoljen procesu koji drži ključ, kao i tip pristupa koji je dozvoljen konkurentnim procesima. Ponekad se režim katanca naziva i stanje (engl. state) katanca.
Objekat (engl. database object) definiše resurs nad kojim se primenjuje operacija zaključavanja. Jedini tip objekta koji se može programerski zaključati jeste tabela (za više informacija, videti sekciju 6.4). SUBP ima pravo da postavi katanac na druge objekte u bazi podataka, kao što su: redovi, prostori tabela, blokovi, particije podataka, i dr.
Trajanje (engl. lock count) definiše dužinu vremena tokom kojeg je katanac bio držan od strane procesa. Trajanje katanca se određuje nivoom izolovanosti pod kojim se naredba pokreće (za više informacija o nivoima izolovanosti, videti sekciju 6.3).
DB2 sistem nudi podršku za različite režime katanaca. Mi ćemo prikazati samo najosnovnije, poređane rastuće po restrikciji koju obezbeđuju:
IN (engl. intent none) definiše da vlasnik katanca može da čita sve podatke u tabeli, uključujući i nepotvrđene podatke, ali ne može da ih menja. Druge konkurentne aplikacije mogu da čitaju ili da ažuriraju tabelu. Ne postavljaju se nikakvi katanci na redovima.
IS (engl. intent share) definiše da vlasnik katanca može da čita proizvoljan podatak u tabeli ako se S katanac može dobiti na redovima ili stranicama od interesa.
IX (engl. intent exclusive) definiše da vlasnik katanca može da čita ili menja proizvoljni podatak u tabeli ako važe naredna dva uslova:
X katanac se može dobiti na redovima ili stranicama koje želimo da menjamo.
S ili U katanac se može dobiti na redovima koje želimo da čitamo.
SIX (engl. share with intent exclusive) definiše da vlasnik katanca može da čita sve podatke iz tabele i da menja redove ako može da dobije X katanac na tim redovima. Za čitanje se ne dobijaju katanci na redovima. Druge konkurentne aplikacije mogu da čitaju iz tabele. SIX katanac se dobija ako aplikacija ima IX katanac na tabeli i onda se zahteva S katanac ili obratno.
S (engl. share) definiše da vlasnik katanca moze da čita proizvoljan podatak u tabeli i neće dobiti katance na redovima ili stranicama.
U (engl. update) definiše da vlasnik katanca može da čita proizvoljan podatak u tabeli i može da menja podatke ako se na tabeli može dobiti X katanac. Pritom se ne dobijaju nikakvi katanci na redovima ili stranicama.
X (engl. exclusive) definiše da vlasnik katanca može da čita i da menja proizvoljan podatak iz tabele. Pritom se ne dobijaju nikakvi katanci na redovima ili stranicama.
Z (engl. super exclusive) katanac se zahteva na tabeli u posebnim prilikama, kao što su recimo menjanje strukture tabele ili njeno brisanje. Nijedna druga aplikacija (ni ona sa nivoom izolovanosti “nepotvrđeno čitanje”, videti sekciju 6.3) ne može da čita niti da ažurira podatke u tabeli.
Očigledno, primenom različitih vrsta katanaca, može doći do sukoba u smislu da li SUBP treba da dodeli katanac nekom aplikacionom procesu kada postoji drugi aplikacioni proces koji već drži katanac nad istim objektom. Samo u slučaju kada su katanci kompatibilni će SUBP dodeliti drugi katanac.
U slučaju da katanci nisu kompatibilni, nije moguće dodeliti traženi katanac aplikacionom procesu. Tada taj proces mora da pređe u stanje čekanja dok proces ne oslobodi nekompatibilni katanac koji drži, kao i dok se svi ostali nekompatibilni katanci ne oslobode. U nekim slučajevima može doći do isteka vremena (engl. timeout) dok zatražilac čeka na katanac. O tome će nešto detaljnije biti reči u nastavku, a za sada ćemo prikazati kompatibilnosti između katanaca u narednoj tabeli. Oznaka N znači da nad resursom nema katanca, odnosno, da aplikacioni proces ne traži katanac.
Konverzija katanca se ostvaruje u situacijama kada aplikacioni proces pristupa objektu nad kojim već drži katanac, a taj pristup zahteva restriktivniji katanac od onog koji se već drži. Proces nad objektom može da drži najviše jedan katanac u nekom trenutku, ali može da traži različite katance nad istim objektom indirektno kroz izvršavanje naredbi.
Promena režima katanca koji se već drži se naziva konverzija katanca (engl. lock conversion).
Neki režimi katanaca se primenjuju samo za tabele, neki samo za redove, blokove ili particije podataka. Za redove ili blokove, konverzija se uglavnom izvršava ukoliko se traži katanac X, a već se drži katanac S ili U.
Katanci IX i S imaju specijalan odnos — nijedan od ova dva katanca se smatra restriktivnijim u odnosu na drugi. Ukoliko aplikacioni proces drži jedan od ova dva katanca i zahteva drugi, onda će se izvršiti konverzija u katanac SIX. Sve ostale konverzije se vrše po narednom principu: Ukoliko je katanac koji se zahteva restriktivniji u odnosu na katanac koji se drži, onda se katanac koji se drži konvertuje u katanac koji se zahteva.
Naravno, da bi se uspešno izvršila konverzija katanca, nad objektom nad kojim se zahteva novi katanac ne sme postojati neki drugi katanac koji je nekompatibilan sa njim.
Da bi se uslužio što veci broj aplikacija, menadžer baza podataka omogucava funkciju ekskalacije katanaca. Ovaj proces objedinjuje proces dobijanja katanca na tabeli i oslobađanja katanaca na redovima. Željeni efekat je da se smanje ukupni zahtevi skladištenja za katance od strane menadžera baza podataka. Ovo ́ce omoguciti drugim aplikacijama da dobiju željene katance. Dva konfiguraciona parametra baze podataka imaju direktan uticaj na proces ekskalacije katanaca:
locklist
: to je prostor u globalnoj memoriji baze podataka koji se koristi za skladištenje katanaca,maxlocks
: to je procenat ukupne liste katanaca koji je dozvoljeno da drži jedna aplikacija.Oba parametra su konfigurabilna.
Ekskalacija katanaca se dešava u dva slučaja:
maxlocks
. U ovoj situaciji menadžer baze podataka ́ce pokušati da oslobodi memorijski prostor dobijanjem jednog katanca na tabelu, a oslobađanjem katanaca na redove te tabele.Ekskalacija katanaca može da ne uspe. Ako se desi greška, aplikacija koja je pokrenula ekskalaciju dobiće vrednost SQLCODE
-912. Ovaj kod bi trebaloda bude obrađen programski od strane aplikacije.
Već smo napomenuli da, ukoliko aplikacija zahteva katanac nad objektom koji je nekompatibilan sa katancem koji već postoji nad istim objektom, ona ulazi u fazu čekanja dok se taj katanac ne oslobodi. Neka, na primer, jedna transakcija čeka na katanac koji drži neka korisnička aplikacija. Ukoliko korisnik koji koristi tu aplikaciju napusti aplikaciju bez da dozvoli potvrđivanje izmena, onda se katanac ne oslobađa nad tim objektom i transakcija može da čeka beskonačno dugo.
SUBP ima poseban mehanizam kojim se može sprečiti da aplikacija beskonačno dugo čeka na oslobađanje katanca i koji se naziva detekcija isteka vremena (engl. lock timeout detection). Ovaj mehanizam se oslanja na korišćenje locktimeout
parametra podešavanja baze podataka i predstavlja najduže vreme koje transakcija može da čeka na oslobađanje katanca. Podrazumevana vrednost ovog parametra je -1
, što znači da je detekcija isteka vremena onemogućena. U nastavku ćemo videti kako možemo postaviti vrednost ovog parametra, kao i koje je ponašanje SUBP u slučaju da dođe do isteka vremena, međutim, prvo ćemo diskutovati o situaciji do koje može doći u slučaju da aplikacije mogu da čekaju beskonačno dugo na oslobađanje katanca.
Pretpostavimo da postoje dve aplikacije A i B koje rade konkurentno i da je mehanizam detekcije isteka vremena onemogućen u SUBP. Aplikacija A se sastoji od dve operacije: prva operacija želi da ažurira red 1 u tabeli 1, a druga operacija želi da ažurira red 2 u tabeli 2. Aplikacija B se takođe sastoji od dve operacije: prva operacija želi da ažurira red 2 u tabeli 2, a druga operacija želi da ažurira red 1 u tabeli 1. Pretpostavimo takođe da se operacije dešavaju u (približno) isto vreme. Redosled izvršavanja ovih operacija i dobijanja katanaca je sledeći (što je ilustrovano i na narednoj slici):
Očigledno, počevši od trenutka T3, obe aplikacije će čekati jedna na drugu beskonačno dugo i neće se “nikad” završiti. Ova pojava se naziva mrtva petlja.
Mrtva petlja (engl. deadlock) predstavlja pojavu kada dve aplikacije (ili više njih) zaključaju podatak koji je neophodan jedan drugoj, što rezultuje u situaciji da se obe aplikacije blokiraju i nijedna ne može da nastavi sa daljim izvršavanjem.
Zbog toga što aplikacije neće “volonterski” osloboditi katance koje drže nad podacima, da bi se prevazišla mrtva petlja, mora se pristupiti procesu prepoznavanja mrtve petlje. Mehanizam prepoznavanja mrtve petlje nadgleda informacije o agentima koji čekaju na katance i pokreće se na interval specifikovan kroz parametar dlchktime
podešavanja baze podataka.
Ukoliko mehanizam pronađe mrtvu petlju, onda se nasumično bira jedan od procesa u mrtvoj petlji koji se naziva žrtva (engl. victim process), čije će izmene biti poništene. Aplikacija koja pripada procesu žrtvi se podiže i prosleđuje joj se odgovarajući kod koji je ona dužna da obradi. SUBP automatski poništava nepotvrđene izmene od strane odabrane aplikacije. Kada je operacija poništavanja završena, katanci koji su pripadali procesu žrtvi se oslobađaju, kursori se zatvaraju i ostali procesi u mrtvoj petlji mogu da nastave dalje sa radom.
Postoje dve vrste grešaka koje SUBP može prijaviti aplikaciji u slučaju da dođe do isteka vremena, odnosno, ako je aplikacija izabrana za žrtvu prilikom prepoznavanja mrtve petlje:
Pokretanjem komande db2 ? sql911
u terminalu je moguće dobiti više informacija o grešci.
Pokretanjem komande db2 ? sql913
u terminalu je moguće dobiti više informacija o grešci.
Još jedan mogući način da se izborimo sa mrtvom petljom jeste da specifikujemo najduži vremenski period koji aplikacija može da čeka na dobijanje katanca. Specijalni registar CURRENT LOCK TIMEOUT
, koji je tipa INTEGER
, sadrži broj sekundi pre nego što SUBP vrati grešku koja indikuje da katanac ne može biti dodeljen aplikacionom procesu.
Validne vrednosti za ovaj registar su u opsegu [-1, 32767]
. Dodatno, specijalnom registru se može postaviti vrednost NULL
. Vrednost -1
označava da SUBP neće prijavljivati istek vremena, već da aplikacija mora da čeka na zahtevani katanac dok se nekompatibilni katanac ne oslobodi ili dok se ne detektuje mrtva petlja. Vrednost 0
označava da aplikacija neće čekati na katanac, već ukoliko ne može da dobije katanac kada ga zatraži, odmah će dobiti grešku. Vrednost NULL
označava da će SUBP koristiti vrednost koja je postavljena u parametru locktimeout
o kojem je bilo reči ranije.
Ukoliko želimo da pročitamo vrednost ovog specijalnog registra, nakon konekcije na bazu podataka potrebno je izvršiti narednu komandu u terminalu:
db2 values CURRENT LOCK TIMEOUT
SQL naredba SET CURRENT LOCK TIMEOUT
služi za promenu vrednosti specijalnog registra CURRENT LOCK TIMEOUT
. Izvršavanje ove naredbe nije pod uticajem kontrole transakcije u programu. Zbog toga, ukoliko ovu naredbu koristimo u našim aplikacijama, dobra je praksa vratiti vrednost na podrazumevanu pre završavanja programa. Sintaksa ove naredbe je data u nastavku:
SET [CURRENT] LOCK TIMEOUT [=]
(WAIT|NOT WAIT|NULL|[WAIT] <CELOBROJNA_KONSTANTA>|<MATIČNA_PROMENLJIVA>)
Ovoj naredbi je moguće specifikovati naredne vrednosti:
Klauzom WAIT
specifikuje se vrednost -1
, što znači da SUBP mora da čeka dok se katanac ne oslobodi ili se ne detektuje mrtva petlja.
Klauzom NOT WAIT
specifikuje se vrednost 0
, što znači da SUBP ne čeka na katance koji se ne mogu dodeliti aplikacijama, već odmah prijavljuje grešku.
Vrednošću NULL
specifikuje se da vrednost u registru CURRENT LOCK TIMEOUT
nije postavljena, čime se efektivno koristi vrednost parametra locktimeout
podešavanja baze podataka. Vrednost koja se vraća za ovaj specijalni registar se menja pri promeni vrednosti parametra locktimeout
.
Klauzom [WAIT] <CELOBROJNA_KONSTANTA>
specifikuje se celobrojna vrednost iz intervala [-1, 32767]
. Ako data vrednost pripada intervalu [1, 32767]
, onda će SUBP čekati toliko sekundi pre nego što aplikaciji prosledi grešku. Vrednosti -1
i 0
odgovaraju specifikovanju prethodno opisanih klauza WAIT
i NO WAIT
, redom.
Navođenjem <MATIČNA PROMENLJIVA>
specifikuje se celobrojna vrednost iz intervala [-1, 32767]
koja se čita iz vrednosti date matične promenljive. Ako data vrednost pripada intervalu [1, 32767]
, onda će SUBP čekati toliko sekundi pre nego što aplikaciji prosledi grešku. Vrednosti -1
i 0
odgovaraju specifikovanju prethodno opisanih klauza WAIT
i NO WAIT
, redom.
Zadatak 6.1: Napisati C/SQL program koji za svaki studijski program pita korisnika da li želi da promeni obim ESPB bodova na tom studijskom programu. Ukoliko korisnik odgovori potvrdno, aplikacija zahteva od korisnika da unese novi broj bodova nakon čega se vrši izmena podataka.
Napisati program tako da može da radi u višekorisničkom okruženju. Postaviti istek vremena za zahtevanje katanaca na 5 sekundi.
Pokrenuti dve instance aplikacije. Uveriti se da dok jedna aplikacija obrađuje tekući studijski program, druga aplikacija ne može da dobije katanac nad odgovarajućim slogom.
Rešenje: Nakon izvršavanja svake SQL naredbe koja može da zahteva zaključavanje nekog sloga u tabeli, potrebno je izvršiti proveru da li aplikacija može da dobije odgovarajući katanac. Provera se sastoji u ispitivanju vrednosti makroa SQLCODE
i to pre poziva funkcije za obradu greške, kako se proces ne bi završio pre obrade katanaca. Sama obrada čekanja je u ovom slučaju vrlo jednostavna - potrebno je ispisati poruku korisniku da je isteklo vreme za čekanje trenutnog katanca i nastaviti dalje sa izvršavanjem (poništiti eventualne izmene i ponovo otvoriti kursor koji se obrađuje).
Datoteka: vezbe/primeri/poglavlje_6/zadatak_6_1.sqc
:
#include <stdio.h>
#include <stdlib.h>
EXEC SQL INCLUDE SQLCA;
EXEC SQL BEGIN DECLARE SECTION;
char hStudyProgramName[201];
short hStudyProgramESPB;
EXEC SQL END DECLARE SECTION;
// Definisemo funkciju koja ce vrsiti obradu cekanja u slucaju da dodje do problema u visekorisnickom okruzenju.
// Primetimo da je definicija funkcije odvojena od njene deklaracije,
// zato sto se u definiciji ove funkcije koristi (otvara) kursor `cStudyPrograms`.
// Ako bismo stavili definiciju ove funkcije ispred `main` funkcije,
// onda bi Db2 pretprocesor izdao upozorenje da se kursor koristi (otvara) pre deklaracije.
// Funkcija vraca 1 ukoliko je doslo do problema, a 0 inace.
int waitForLock();
// Funkcija za obradu SQL gresaka
void checkSQL(const char *str);
int main() {
EXEC SQL CONNECT TO stud2020 USER student USING abcdef;
checkSQL("Connect");
// Postavljanje isteka vremena na 5 sekundi
EXEC SQL SET CURRENT LOCK TIMEOUT 5;
checkSQL("Set current lock timeout 5");
EXEC SQL
DECLARE cStudyPrograms CURSOR WITH HOLD FOR
SELECT TRIM(NAZIV),
OBIMESPB
FROM DA.STUDIJSKIPROGRAM
FOR UPDATE OF OBIMESPB;
checkSQL("Declare cStudyPrograms");
EXEC SQL OPEN cStudyPrograms;
checkSQL("Open cStudyPrograms");
for(;;) {
// Naredba FETCH dohvata jedan red iz rezultata upita.
// Bilo da li se informacije koriste za citanje ili za eventualnu izmenu,
// SUBP mora da dodeli aplikaciji odgovarajuci katanac.
EXEC SQL
FETCH cStudyPrograms
INTO :hStudyProgramName,
:hStudyProgramESPB;
if (waitForLock()) {
continue;
}
checkSQL("Fetch cStudyPrograms");
if (SQLCODE == 100) {
printf("Nema vise studijskih programa za obradjivanje!\n");
break;
}
printf("%s (%hd)\n", hStudyProgramName, hStudyProgramESPB);
printf("Da li zelite da promenite bodove za ovaj studijski program? [d/n] ");
char userResponse = getchar();
getchar(); // novi red
if (userResponse != 'd') {
continue;
}
printf("Unesite broj bodova: ");
scanf("%hd", &hStudyProgramESPB);
// Izvrsavanje naredbe UPDATE uvek trazi katanac za azuriranje podataka
EXEC SQL
UPDATE DA.STUDIJSKIPROGRAM
SET OBIMESPB = :hStudyProgramESPB
WHERE CURRENT OF cStudyPrograms;
if (waitForLock()) {
continue;
}
checkSQL("Update");
printf("Bodovi su azurirani!\n\n");
}
EXEC SQL CLOSE cStudyPrograms;
checkSQL("Close cStudyPrograms");
// Vracamo istek vremena na podrazumevanu vrednost
EXEC SQL SET CURRENT LOCK TIMEOUT NULL;
checkSQL("Set current lock timeout null");
EXEC SQL COMMIT;
checkSQL("Potvrdjivanje izmena");
EXEC SQL CONNECT RESET;
checkSQL("Connect reset");
return 0;
}
void checkSQL(const char *str) {
if(SQLCODE < 0) {
fprintf(stderr, "Greska %d: %s\n", SQLCODE, str);
EXEC SQL ROLLBACK;
EXEC SQL CONNECT RESET;
exit(EXIT_FAILURE);
}
}
int waitForLock() {
if (-913 <= SQLCODE && SQLCODE <= -911) {
printf("Objekat je zakljucan od strane druge transakcije. "
"Sacekati neko vreme...\n");
EXEC SQL ROLLBACK;
checkSQL("Rollback");
EXEC SQL OPEN cStudyPrograms;
checkSQL("Open cStudyPrograms - waitForLock");
return 1;
}
return 0;
}
Problem sa pristupom obrade grešaka u prethodnom zadatku je taj što se pod transakcijom smatra izvršavanje celog programa, te ako bilo gde u obradi dođe do problema dobijanja katanaca, sve izmene koje su do tada načinjene se poništavaju. Umesto toga, pouzdanija tehnika jeste definisati da jedna transakcija obuhvata obradu jednog podatka. U prethodnom primeru, to bi značilo da jedna transakcija obuhvata izmenu tačno jednog studijskog programa, a ne svih studijskih programa. Dodatno, često je poželjno da aplikacija pamti koji podaci su već obrađeni, kako ih ne bi obrađivala dva puta. Ovo je važno pamtiti zbog toga što otvaranjem kursora u obradi čekanja, kursor se pozicionira na početak rezultujuće tabele, a ne tamo gde je obrada stala. (Alternativno, možemo pamtiti broj obrađenih redova u neku matičnu promenljivu row_count
, pa zatim koristiti klauzu OFFSET :row_count ROWS
u SELECT
naredbi.)
Naredni zadatak ilustruje obradu transakcija u višekorisničkom okruženju korišćenjem znanja iz ovog poglavlja.
Zadatak 6.2: Napisati C/SQL program koji za svaki predmet omogućava korisniku da poveća broj ESPB za 1. Nakon svakog ažuriranja, proveriti da li je odgovarajuci red u tabeli izmenjen ponovnim dohvatanjem informacija.
Napisati program tako da može da radi u višekorisničkom okruženju. Obrada jednog predmeta predstavlja jednu transakciju. Postaviti istek vremena za zahtevanje katanaca na 10 sekundi.
Rešenje: S obzirom da se ovoga puta obrada katanaca vrši kao deo zasebne transakcije, onda je potrebno da poništavamo izmene ukoliko dođe do isteka vremena za dobijanje katanaca. Zbog toga, funkciji za obradu čekanja dodajemo naredbu ROLLBACK
. Dodatno, kako će ta naredba zatvoriti sve otvorene kursore u transakciji (pa čak i one deklarisane klauzom WITH HOLD
), potrebno je otvoriti kursor koji se koristio pre nego što se pređe u narednu iteraciju petlje koja obrađuje taj kursor.
Takođe, primetimo da aplikacija čuva niz obrađenih predmeta tokom svog rada. Prilikom obrađivanja narednog predmeta, aplikacija prvo proverava funkcijom int isCourseProcessed(sqlint32 id)
i ukoliko jeste, aplikacija ga preskače i prelazi na naredni predmet. U suprotnom, vrši se obrada i funkcijom void setCourseAsProcessed(sqlint32 id)
se predmet označava da je obrađen za naredne provere.
Datoteka: vezbe/primeri/poglavlje_6/zadatak_6_2.sqc
:
#include <stdio.h>
#include <stdlib.h>
EXEC SQL INCLUDE SQLCA;
EXEC SQL BEGIN DECLARE SECTION;
sqlint32 hCourseId;
char hCourseLabel[21],
hCourseName[151];
short hCourseESPB;
EXEC SQL END DECLARE SECTION;
// Niz identifikatora predmeta obradjenih od strane ovog programa.
// Ovde koristimo staticki niz radi ilustracije, ali broj predmeta nije poznat unapred, naravno,
// pa bi korektnije resenje koristilo dinamicki inicijalizovan niz.
sqlint32 processedCoursesArr[1000];
size_t processedCoursesArrSize = 0u;
// Pomocne funkcije sa rad sa nizom identifikatora predmeta
int isCourseProcessed(sqlint32 id);
void setCourseAsProcessed(sqlint32 id);
// Pomocne funkcije za obradu gresaka i cekanja na katance
void checkSQL(const char *str);
int waitForLock();
int main() {
EXEC SQL CONNECT TO stud2020 USER student USING abcdef;
checkSQL("Connect");
EXEC SQL SET CURRENT LOCK TIMEOUT 5;
checkSQL("Set current lock timeout 5");
EXEC SQL
DECLARE cCourses CURSOR WITH HOLD FOR
SELECT ID,
TRIM(OZNAKA),
TRIM(NAZIV),
ESPB
FROM DA.PREDMET
FOR UPDATE OF ESPB;
checkSQL("Declare cCourses");
EXEC SQL OPEN cCourses;
checkSQL("Open cCourses");
printf("PREDMETI\n");
for(;;) {
EXEC SQL
FETCH cCourses
INTO :hCourseId,
:hCourseLabel,
:hCourseName,
:hCourseESPB;
if (waitForLock()) {
continue;
}
checkSQL("Fetch cCourses");
if (SQLCODE == 100) {
printf("Nema vise predmeta za obradjivanje!\n");
break;
}
// Provera da li je predmet vec obradjen
// od strane ove instance aplikacije
if (isCourseProcessed(hCourseId)) {
continue;
}
printf("\n---------------------------------------------------------------\n");
printf("%5.5d %-10.10s %-40.40s %5.5d", hCourseId, hCourseLabel, hCourseName, hCourseESPB);
printf("\n---------------------------------------------------------------\n");
printf("\nDa li zelite da uvecate broj ESPB za ovaj predmet za 1? [d/n] ");
char userResponse = getchar();
getchar(); // novi red
if (userResponse == 'd') {
// Azuriranje podataka zahteva katanac za menjanje podataka
EXEC SQL
UPDATE DA.PREDMET
SET ESPB = ESPB + 1
WHERE CURRENT OF cCourses;
if (waitForLock()) {
continue;
}
checkSQL("Update");
// Slicno kao i FETCH, naredba SELECT INTO zahteva katanac za citanje
EXEC SQL
SELECT ESPB
INTO :hCourseESPB
FROM DA.PREDMET
WHERE ID = :hCourseId;
if (waitForLock()) {
continue;
}
checkSQL("Select into");
printf("Broj ESPB je sada %hd\n", hCourseESPB);
}
// Evidentiramo izmene u nizu namenjenim za to.
setCourseAsProcessed(hCourseId);
// Potvrdjivanje izmena u tekucoj transakciji
EXEC SQL COMMIT;
checkSQL("Commit");
}
EXEC SQL CLOSE cCourses;
checkSQL("Close cCourses");
// Vracamo istek vremena na podrazumevanu vrednost
EXEC SQL SET CURRENT LOCK TIMEOUT NULL;
checkSQL("Set current lock timeout null");
EXEC SQL COMMIT;
checkSQL("Potvrdjivanje izmena");
EXEC SQL CONNECT RESET;
checkSQL("Connect reset");
return 0;
}
void checkSQL(const char *str) {
if(SQLCODE < 0) {
fprintf(stderr, "Greska %d: %s\n", SQLCODE, str);
EXEC SQL ROLLBACK;
EXEC SQL CONNECT RESET;
exit(EXIT_FAILURE);
}
}
int waitForLock() {
if (-913 <= SQLCODE && SQLCODE <= -911) {
printf("Objekat je zakljucan od strane druge transakcije. "
"Sacekati neko vreme...\n");
EXEC SQL ROLLBACK;
checkSQL("Rollback");
EXEC SQL OPEN cCourses;
checkSQL("Open cCourses - obrada cekanja");
return 1;
}
return 0;
}
int isCourseProcessed(sqlint32 id) {
for (unsigned i = 0; i < processedCoursesArrSize; ++i) {
if (processedCoursesArr[i] == id) {
return 1;
}
}
return 0;
}
void setCourseAsProcessed(sqlint32 id) {
processedCoursesArr[processedCoursesArrSize++] = id;
}
U najosnovnijem slučaju, svaka transakcija je u potpunosti izolovana od svih drugih transakcija. Međutim, videli smo da ovakva, potpuna izolovanost, može brzo dovesti do problema poput mrtve petlje u konkurentnom sistemu izvršavanja. Zbog toga, da bi se povećala konkurentnost između aplikacionih procesa, DB2 SUBP definiše različite nivoe izolovanosti na kojima aplikacija može da se izvršava.
Nivo izolovanosti (engl. isolation level) koji se pridružuje aplikacionom procesu određuje stepen zaključavanja ili izolacije u kojem su podaci kojima taj proces pristupa, u odnosu na druge konkurentne procese.
Drugim rečima, nivo izolovanosti jednog aplikacionog procesa P specifikuje sledeće:
DB2 definiše naredna četiri nivoa izolacije:
U nivou izolacije ponovljeno čitanje (engl. repeatable read, skr. RR) nijedan podatak koji je pročitan od strane procesa P ne sme se menjati od strane drugih procesa sve do završetka obrade od strane procesa P (čime se obezbeđuje saglasnost podataka u slučaju ponovljenog čitanja, te otuda i naziv nivoa izolacije). Sa druge strane, nijedan red promenjen od strane drugog procesa ne može se čitati od strane procesa P dok se promene u okviru tog procesa na okončaju.
Osim već pomenutih ekskluzivnih katanaca, proces na RR nivou izolovanosti dobija bar deljive katance na svim podacima kojima pristupa, pri čemu se zaključavanje vrši tako da je proces u potpunosti izolovan od strane ostalih procesa.
U nivou izolacije stabilno čitanje (engl. read stability, skr. RS) nijedan podatak koji je pročitan od strane procesa P ne sme se menjati od strane drugih procesa sve do završetka obrade od strane procesa P. Sa druge strane, nijedan red promenjen od strane drugog procesa ne može se čitati dok se promene u okviru tog procesa ne okončaju.
Međutim, za razliku od RR, na ovom nivou izolacije proces koji izvršava isti upit više puta, može dobiti neke nove redove u odgovorima u slučaju da drugi proces unese nove redove koji zadovoljavaju dati upit i potvrdi te izmene. Ovo predstavlja tzv. problem fantomskih redova (engl. fantom rows problem).
Osim ekskluzivnih katanaca, proces na RS nivou izolovanosti dobija bar deljive katance na svim podacima koji su prihvaćeni. Na primer, neka se u procesu P izvršava naredni upit:
SELECT *
FROM DA.STUDIJSKIPROGRAM
WHERE IDNIVOA = 1
U slučaju RR nivoa izolovanosti izvršavanje ovog upita izazvaće zaključavanje svih redova tabele STUDIJSKIPROGRAM
, a u slučaju RS nivoa izolovanosti zaključavanje se vrši samo nad onim redovima koji ispunjavaju uslov restrikcije IDNIVOA = 1
. Zbog toga, ako neki drugi proces unese novi red koji zadovoljava datu restrikciju, u slučaju RR nivoa izolovanosti to neće biti moguće, dok će ponovo izvršavanje upita u slučaju RS nivoa izolovanosti proizvesti prikazivanje i tih novih redova.
U nivou izolacije stabilni kursor (engl. cursor stability, skr. CS) nijedan proces ne može da menja podatke iz reda dok je ažurirajući kursor pozicioniran nad tim redom. Dakle, ovaj nivo izolovanosti obezbeđuje da konkurentni procesi ne mogu menjati samo trenutno aktivne redove otvorenih kursora posmatranog procesa P. Redovi koji su ranije pročitani mogu biti menjani. Takodje, nijedan red promenjen od strane drugog procesa ne može se čitati dok se promene u okviru tog procesa na okončaju.
Takođe, ovaj nivo izolovanosti zaključava sve redove kojima transakcija pristupa dok je kursor pozicioniran na tim redovima. Ako transakcija koristi podatke iz reda za čitanje, onda se ovaj katanac drži sve dok se ne dohvati naredni red u kursoru ili dok se transakcija ne završi. Međutim, ako je izmena izvršena nad nekim redom, onda se nad tim redom drži katanac sve do kraja transakcije.
Očito, i u ovom nivou izolacije moguć je problem fantomskih redova. Međutim, postoji još jedan problem koji se može javiti. Neka prvo proces A pročita red, pa zatim pređe na druge redove. Neka zatim proces B pročita i izmeni isti red i potvrdi svoje izmene. Ukoliko proces A ponovo pročita isti red, videće drugačije vrednosti u odnosu na prethodno čitanje. Ovaj problem se naziva problem neponovljivih čitanja (engl. non-repeatable reads problem).
Bez obzira na razne ekskluzivne katance, ovaj nivo obezbeđuje bar deljive katance na aktivnim redovima svih kursora.
I nivou izolacije nepotvrđeno čitanje (engl. uncommitted read, skr. UR) proces P može da pristupi nepotvrđenim izmenama od strane drugih procesa. Dodatno, nivo izolacije UR ne sprečava drugim procesima da pristupe redu koji čita proces P, osim ukoliko taj drugi proces ne pokušava da izmeni strukturu tabele ili da je obriše.
Za naredbe SELECT INTO
ili FETCH
nad kursorom koji služi samo za čitanje, ili podupite naredbi INSERT
ili UPDATE
, ili upite koji kao rezultat imaju skalarnu vrednost, ovaj nivo izolacije omogućava da:
Bilo koji red pročitan tokom jedinice obrade bude promenjen od strane drugih procesa.
Proces može čitati podatke izmenjene od strane drugih korisnika, čak i pre nego što su te izmene potvrđene (ne traži se deljeni katanac za čitanje).
Za ostale operacije važe pravila nivoa izolovanosti CS.
Pored problema fantomskih redova i problema neponovljivih čitanja, postoji još jedan problem koji se može javiti u ovom nivou izolacije. Ako prvo proces A izmeni neku vrednost, zatim proces B pročita tu vrednost pre nego što je potvrđena, a zatim proces A poništi svoje izmene, onda proces B može raditi nad nekorektnim podacima. Ovaj problem se naziva problem pristupa nepotvrđenim podacima (engl. access to uncommitted data problem).
Moguće je specifikovati koji se nivo izolovanosti koristi i on je u efektu tokom trajanja jedinice posla. Postoji nekoliko načina za podešavanje nivoa izolovanosti:
WITH
1, čija je sintaksa data u nastavku:WITH (
RR [<KLAUZA_ZAKLJUČAVANJA>] |
RS [<KLAUZA_ZAKLJUČAVANJA>] |
CS |
UR
)
gde je <KLAUZA_ZAKLJUČAVANJA>
klauza kojom se specifikuje tip katanca koji aplikacija zahteva od SUBP, a čija je sintaksa:
USE AND KEEP (SHARE|UPDATE|EXCLUSIVE) LOCKS
WITH
klauza se može koristiti u narednim naredbama: DECLARE CURSOR
, pretražujuća UPDATE
naredba, INSERT
, SELECT
, SELECT INTO
i pretražujuća DELETE
naredba.
CURRENT ISOLATION
, čiji je tip CHAR(2)
, a koji sadrži informaciju o nivou izolacije za svaku dinamičku SQL naredbu koja se izvršava u tekućoj sesiji. Moguće vrednosti su: prazna niska, 'RR'
, 'RS'
, 'CS'
i 'UR'
. Promena ove vrednosti se može izvršiti naredbom SET CURRENT ISOLATION
, čija je sintaksa:SET [CURRENT] ISOLATION [=] (UR|CS|RR|RS|RESET)
Navođenjem vrednosti RESET
će vrednost registra biti postavljena na praznu nisku, što znači da se neće koristiti ta vrednost za registar.
ISOLATION
opcije prilikom izvršavanja naredbi PRECOMPILE
ili BIND
. Ova opcija ima narednu sintaksu:ISOLATION (CS|RR|RS|UR)
Ukoliko se ne specifikuje nivo izolacije, DB2 će podrazumevano koristiti nivo CS.
Naredni primeri ilustruju različite efekte iste aplikacije pri različitim nivoima izolovanosti. Primetimo da smo u narednim primerima koristili klauzu WITH
za postavljanje nivoa izolovanosti.
Zadatak 6.3: Napisati:
repeatableRead
koji dva puta ispisuje informacije o godini, oznaci, nazivu i periodu prijavljivanja za svaki ispitni rok u 2021. godini. Omogućiti da se prilikom oba ispisivanja dobijaju iste informacije.insertIspitniRok
koji unosi novi ispitni rok za mesec mart u 2021. godini. Postaviti istek vremena za zahtevanje katanaca na 5 sekundi.Rešenje: Za potrebe prevođenja i pripremanja podataka u bazi, napravljena je Makefile
datoteka čiji je sadržaj:
Datoteka: vezbe/primeri/poglavlje_6/zadatak_6_3/Makefile
:
# Za prevodjenje programa pozvati: make
# Za pripremu baze pozvati: make db
# Za ciscenje artefakta pozvati: make clean
repeatableRead: repeatableRead.sqc insertIspitniRok
./prevodjenje repeatableRead stud2020 student abcdef
insertIspitniRok: insertIspitniRok.sqc
./prevodjenje insertIspitniRok stud2020 student abcdef
.PHONY: clean db
clean:
rm -f *.bnd
rm -f *.o
rm -f *.c
rm -f repeatableRead
rm -f insertIspitniRok
db:
db2 -tf pripremaBaze.sql || true
Prevođenje programa se vrši pozivom alata make
, a moguće je i očistiti artefakte prevođenja pozivom make clean
. Pre ilustracije rada programa repeatableRead
, potrebno je pozvati make db
koji će pripremiti informacije o ispitnim rokovima u bazu podataka na osnovu skripta pripremaBaze.sql
čiji je sadržaj:
Datoteka: vezbe/primeri/poglavlje_6/zadatak_6_3/pripremaBaze.sql
:
CONNECT TO STUD2020 USER student USING abcdef;
DELETE FROM DA.ISPITNIROK
WHERE SKGODINA = 2021;
DELETE FROM DA.SKOLSKAGODINA
WHERE SKGODINA = 2021;
INSERT INTO DA.SKOLSKAGODINA
VALUES (2021, '09/01/2021', '09/01/2022');
INSERT INTO DA.ISPITNIROK
VALUES (2021, 'Jan21', 'Januar 2021', '01/01/2022', '01/11/2022'),
(2021, 'Feb21', 'Februar 2021', '02/01/2022', '02/11/2022');
CONNECT RESET;
Pokrenuti program repeatableRead
i započeti prvo ispisivanje. U toku prvog ispisivanja ili nakon njega, pokrenuti program insertIspitniRok
, pa nastaviti sa daljim ispisivanjem ispitnih rokova u programu repeatableRead
. Uveriti se da program insertIspitniRok
ne može da izvrši unos sve dok program repeatableRead
ne završi sa svim ispisivanjima i, takođe, da oba ispisivanja u tom programu imaju iste vrednosti.
Datoteka: vezbe/primeri/poglavlje_6/zadatak_6_3/repeatableRead.sqc
:
#include <stdio.h>
#include <stdlib.h>
EXEC SQL INCLUDE SQLCA;
EXEC SQL BEGIN DECLARE SECTION;
short hSchoolYear;
char hFinalsLabel[21],
hFinalsName[31],
hFinalsStartDate[11],
hFinalsEndDate[11];
EXEC SQL END DECLARE SECTION;
void checkSQL(const char *str) {
if(SQLCODE < 0) {
fprintf(stderr, "Greska %d: %s\n", SQLCODE, str);
EXEC SQL ROLLBACK;
EXEC SQL CONNECT RESET;
exit(EXIT_FAILURE);
}
}
int main() {
EXEC SQL CONNECT TO stud2020 USER student USING abcdef;
checkSQL("Connect");
EXEC SQL
DECLARE cFinals CURSOR FOR
SELECT SKGODINA,
OZNAKAROKA,
NAZIV,
DATPOCETKA,
DATKRAJA
FROM DA.ISPITNIROK
WHERE SKGODINA = 2021
FOR READ ONLY
WITH RR;
checkSQL("Declare cursor");
int i = 1;
for(; i<3; ++i)
{
printf("\nCitanje rezultata %d. put. Pritisnite ENTER za pocetak.\n", i);
getchar();
EXEC SQL OPEN cFinals;
checkSQL("Open");
printf("\n---------------------------------------------------------\n");
for(;;)
{
EXEC SQL
FETCH cFinals
INTO :hSchoolYear,
:hFinalsLabel,
:hFinalsName,
:hFinalsStartDate,
:hFinalsEndDate;
checkSQL("Fetch");
if(SQLCODE == 100) {
break;
}
printf("%4.4d %-10.10s %-20.20s %-10.10s %-10.10s\n", hSchoolYear, hFinalsLabel, hFinalsName, hFinalsStartDate, hFinalsEndDate);
}
printf("\n---------------------------------------------------------\n");
EXEC SQL CLOSE cFinals;
checkSQL("Close");
}
EXEC SQL COMMIT;
checkSQL("Potvrdjivanje izmena");
EXEC SQL CONNECT RESET;
checkSQL("Connect reset");
return 0;
}
Datoteka: vezbe/primeri/poglavlje_6/zadatak_6_3/insertIspitniRok.sqc
:
#include <stdio.h>
#include <stdlib.h>
EXEC SQL INCLUDE SQLCA;
void checkSQL(const char *str) {
if(SQLCODE < 0) {
fprintf(stderr, "Greska %d: %s\n", SQLCODE, str);
EXEC SQL ROLLBACK;
EXEC SQL CONNECT RESET;
exit(EXIT_FAILURE);
}
}
int waitForLock(const char *code_hint) {
if (-913 <= SQLCODE && SQLCODE <= -911) {
printf("[%s] Objekat je zakljucan od strane druge transakcije. "
"Sacekati neko vreme...\n", code_hint);
EXEC SQL ROLLBACK;
checkSQL("Rollback");
return 1;
}
return 0;
}
int main() {
EXEC SQL CONNECT TO stud2020 USER student USING abcdef;
checkSQL("Connect");
EXEC SQL SET CURRENT LOCK TIMEOUT 5;
checkSQL("Set timeout");
for (;;) {
EXEC SQL
INSERT INTO DA.ISPITNIROK
VALUES (2021, 'Mar21', 'Mart 2021', '03/01/2022', '03/11/2022');
if (waitForLock("INSERT")) {
continue;
}
checkSQL("Insert");
if (SQLCODE == 0) {
break;
}
};
EXEC SQL SET CURRENT LOCK TIMEOUT NULL;
checkSQL("Set timeout");
EXEC SQL COMMIT;
checkSQL("Potvrdjivanje izmena");
EXEC SQL CONNECT RESET;
checkSQL("Connect reset");
return 0;
}
Zadatak 6.4: Napisati:
readStability
koji dva puta ispisuje informacije o godini, oznaci, nazivu i periodu prijavljivanja za svaki ispitni rok u 2021. godini. Dozvoljeno je da se prilikom drugog ispisivanja pojave novi redovi, ali ne i da budu vidljive izmene pročitanih redova.insertIspitniRok
koji unosi novi ispitni rok za mesec mart u 2021. godini. Postaviti istek vremena za zahtevanje katanaca na 5 sekundi.updateIspitniRok
koji za svaki ispitni rok u 2021. godini produžava period prijavljivanja za 3 dana. Postaviti istek vremena za zahtevanje katanaca na 5 sekundi.Rešenje: Kao i u prethodnom zadatku, napravljena je Makefile
koja služi za pozivanje alata make
na već opisan način.
Pokrenuti program readStability
i započeti prvo ispisivanje. U toku ispisivanja ili nakon njega, pokrenuti program insertIspitniRok
, pa nastaviti sa daljim ispisivanjem ispitnih rokova. Uveriti se da program insertIspitniRok
može da izvrši unos novog ispitnog roka tokom rada programa readStability
i da se novi rok vidi prilikom drugog ispisivanja. Zatim, ponovo pokrenuti program readStability
(nakon ponovnog pripremanja baze podataka pozivom make db
), pa u toku prvog ispisivanja ili nakon njega, pokrenuti program updateIspitniRok
. Uveriti se da program updateIspitniRok
ne može da promeni informacije o ispitnim rokovima dok program readStability
radi.
Datoteka: vezbe/primeri/poglavlje_6/zadatak_6_4/readStability.sqc
:
#include <stdio.h>
#include <stdlib.h>
EXEC SQL INCLUDE SQLCA;
EXEC SQL BEGIN DECLARE SECTION;
short hSchoolYear;
char hFinalsLabel[21],
hFinalsName[31],
hFinalsStartDate[11],
hFinalsEndDate[11];
EXEC SQL END DECLARE SECTION;
void checkSQL(const char *str) {
if(SQLCODE < 0) {
fprintf(stderr, "Greska %d: %s\n", SQLCODE, str);
EXEC SQL ROLLBACK;
EXEC SQL CONNECT RESET;
exit(EXIT_FAILURE);
}
}
int main() {
EXEC SQL CONNECT TO stud2020 USER student USING abcdef;
checkSQL("Connect");
EXEC SQL
DECLARE cFinals CURSOR FOR
SELECT SKGODINA,
OZNAKAROKA,
NAZIV,
DATPOCETKA,
DATKRAJA
FROM DA.ISPITNIROK
WHERE SKGODINA = 2021
FOR READ ONLY
WITH RS;
checkSQL("Declare cursor");
int i = 1;
for(; i<3; ++i)
{
printf("\nCitanje rezultata %d. put. Pritisnite ENTER za pocetak.\n", i);
getchar();
EXEC SQL OPEN cFinals;
checkSQL("Open");
printf("\n---------------------------------------------------------\n");
for(;;)
{
EXEC SQL
FETCH cFinals
INTO :hSchoolYear,
:hFinalsLabel,
:hFinalsName,
:hFinalsStartDate,
:hFinalsEndDate;
checkSQL("Fetch");
if(SQLCODE == 100) {
break;
}
printf("%4.4d %-10.10s %-20.20s %-10.10s %-10.10s\n", hSchoolYear, hFinalsLabel, hFinalsName, hFinalsStartDate, hFinalsEndDate);
}
printf("\n---------------------------------------------------------\n");
EXEC SQL CLOSE cFinals;
checkSQL("Close");
}
EXEC SQL COMMIT;
checkSQL("Potvrdjivanje izmena");
EXEC SQL CONNECT RESET;
checkSQL("Connect reset");
return 0;
}
Datoteka: vezbe/primeri/poglavlje_6/zadatak_6_4/insertIspitniRok.sqc
:
#include <stdio.h>
#include <stdlib.h>
EXEC SQL INCLUDE SQLCA;
void checkSQL(const char *str) {
if(SQLCODE < 0) {
fprintf(stderr, "Greska %d: %s\n", SQLCODE, str);
EXEC SQL ROLLBACK;
EXEC SQL CONNECT RESET;
exit(EXIT_FAILURE);
}
}
int waitForLock(const char *code_hint) {
if (-913 <= SQLCODE && SQLCODE <= -911) {
printf("[%s] Objekat je zakljucan od strane druge transakcije. "
"Sacekati neko vreme...\n", code_hint);
EXEC SQL ROLLBACK;
checkSQL("Rollback");
return 1;
}
return 0;
}
int main() {
EXEC SQL CONNECT TO stud2020 USER student USING abcdef;
checkSQL("Connect");
EXEC SQL SET CURRENT LOCK TIMEOUT 5;
checkSQL("Set timeout");
for (;;) {
EXEC SQL
INSERT INTO DA.ISPITNIROK
VALUES (2021, 'Mar21', 'Mart 2021', '03/01/2022', '03/11/2022');
if (waitForLock("INSERT")) {
continue;
}
checkSQL("Insert");
if (SQLCODE == 0) {
break;
}
};
EXEC SQL SET CURRENT LOCK TIMEOUT NULL;
checkSQL("Set timeout");
EXEC SQL COMMIT;
checkSQL("Potvrdjivanje izmena");
EXEC SQL CONNECT RESET;
checkSQL("Connect reset");
return 0;
}
Datoteka: vezbe/primeri/poglavlje_6/zadatak_6_4/updateIspitniRok.sqc
:
#include <stdio.h>
#include <stdlib.h>
EXEC SQL INCLUDE SQLCA;
void checkSQL(const char *str) {
if(SQLCODE < 0) {
fprintf(stderr, "Greska %d: %s\n", SQLCODE, str);
EXEC SQL ROLLBACK;
EXEC SQL CONNECT RESET;
exit(EXIT_FAILURE);
}
}
int waitForLock(const char *code_hint) {
if (-913 <= SQLCODE && SQLCODE <= -911) {
printf("[%s] Objekat je zakljucan od strane druge transakcije. "
"Sacekati neko vreme...\n", code_hint);
EXEC SQL ROLLBACK;
checkSQL("Rollback");
return 1;
}
return 0;
}
int main() {
EXEC SQL CONNECT TO stud2020 USER student USING abcdef;
checkSQL("Connect");
EXEC SQL SET CURRENT LOCK TIMEOUT 5;
checkSQL("Set timeout");
for (;;) {
EXEC SQL
UPDATE DA.ISPITNIROK
SET DATKRAJA = DATKRAJA + 3 DAYS
WHERE SKGODINA = 2021;
if (waitForLock("UPDATE")) {
continue;
}
checkSQL("Insert");
if (SQLCODE == 0) {
break;
}
};
EXEC SQL SET CURRENT LOCK TIMEOUT NULL;
checkSQL("Set timeout");
EXEC SQL COMMIT;
checkSQL("Potvrdjivanje izmena");
EXEC SQL CONNECT RESET;
checkSQL("Connect reset");
return 0;
}
Zadatak 6.5: Napisati:
cursorStability
koji dva puta ispisuje informacije o godini, oznaci, nazivu i periodu prijavljivanja za svaki ispitni rok u 2021. godini. Dozvoljeno je da se prilikom drugog ispisivanja pojave novi redovi i da budu vidljive izmene pročitanih redova, ali ne i nepotvrđene izmene.insertIspitniRok
koji unosi novi ispitni rok za mesec mart u 2021. godini. Postaviti istek vremena za zahtevanje katanaca na 5 sekundi.updateIspitniRok
koji za svaki ispitni rok u 2021. godini produžava period prijavljivanja za 3 dana. Postaviti istek vremena za zahtevanje katanaca na 5 sekundi.Rešenje: Kao i u prethodnom zadatku, napravljena je Makefile
koja služi za pozivanje alata make
na već opisan način.
Pokrenuti program cursorStability
i započeti prvo ispisivanje. U toku ispisivanja ili nakon njega, pokrenuti program insertIspitniRok
, pa nastaviti sa daljim ispisivanjem ispitnih rokova. Uveriti se da program insertIspitniRok
može da izvrši unos novog ispitnog roka tokom rada programa readStability
i da se novi rok vidi prilikom drugog ispisivanja. Zatim, ponovo pokrenuti program readStability
(nakon ponovnog pripremanja baze podataka pozivom make db
), pa u toku prvog ispisivanja ili nakon njega, pokrenuti program updateIspitniRok
. Uveriti se da program updateIspitniRok
ovoga puta može da promeni informacije o ispitnim rokovima, čak i dok program cursorStability
radi, kao i da će ovoga puta program cursorStability
videti te izmene prilikom drugog ispisivanja, što nije bio slučaj za nivo izolovanosti RS ili strožijim od njega.
Datoteka: vezbe/primeri/poglavlje_6/zadatak_6_5/cursorStability.sqc
:
#include <stdio.h>
#include <stdlib.h>
EXEC SQL INCLUDE SQLCA;
EXEC SQL BEGIN DECLARE SECTION;
short hSchoolYear;
char hFinalsLabel[21],
hFinalsName[31],
hFinalsStartDate[11],
hFinalsEndDate[11];
EXEC SQL END DECLARE SECTION;
void checkSQL(const char *str) {
if(SQLCODE < 0) {
fprintf(stderr, "Greska %d: %s\n", SQLCODE, str);
EXEC SQL ROLLBACK;
EXEC SQL CONNECT RESET;
exit(EXIT_FAILURE);
}
}
int main() {
EXEC SQL CONNECT TO stud2020 USER student USING abcdef;
checkSQL("Connect");
EXEC SQL
DECLARE cFinals CURSOR FOR
SELECT SKGODINA,
OZNAKAROKA,
NAZIV,
DATPOCETKA,
DATKRAJA
FROM DA.ISPITNIROK
WHERE SKGODINA = 2021
FOR READ ONLY
WITH CS;
checkSQL("Declare cursor");
int i = 1;
for(; i<3; ++i)
{
printf("\nCitanje rezultata %d. put. Pritisnite ENTER za pocetak.\n", i);
getchar();
EXEC SQL OPEN cFinals;
checkSQL("Open");
printf("\n---------------------------------------------------------\n");
for(;;)
{
EXEC SQL
FETCH cFinals
INTO :hSchoolYear,
:hFinalsLabel,
:hFinalsName,
:hFinalsStartDate,
:hFinalsEndDate;
checkSQL("Fetch");
if(SQLCODE == 100) {
break;
}
printf("%4.4d %-10.10s %-20.20s %-10.10s %-10.10s\n", hSchoolYear, hFinalsLabel, hFinalsName, hFinalsStartDate, hFinalsEndDate);
}
printf("\n---------------------------------------------------------\n");
EXEC SQL CLOSE cFinals;
checkSQL("Close");
}
EXEC SQL COMMIT;
checkSQL("Potvrdjivanje izmena");
EXEC SQL CONNECT RESET;
checkSQL("Connect reset");
return 0;
}
Datoteka: vezbe/primeri/poglavlje_6/zadatak_6_5/insertIspitniRok.sqc
:
#include <stdio.h>
#include <stdlib.h>
EXEC SQL INCLUDE SQLCA;
void checkSQL(const char *str) {
if(SQLCODE < 0) {
fprintf(stderr, "Greska %d: %s\n", SQLCODE, str);
EXEC SQL ROLLBACK;
EXEC SQL CONNECT RESET;
exit(EXIT_FAILURE);
}
}
int waitForLock(const char *code_hint) {
if (-913 <= SQLCODE && SQLCODE <= -911) {
printf("[%s] Objekat je zakljucan od strane druge transakcije. "
"Sacekati neko vreme...\n", code_hint);
EXEC SQL ROLLBACK;
checkSQL("Rollback");
return 1;
}
return 0;
}
int main() {
EXEC SQL CONNECT TO stud2020 USER student USING abcdef;
checkSQL("Connect");
EXEC SQL SET CURRENT LOCK TIMEOUT 5;
checkSQL("Set timeout");
for (;;) {
EXEC SQL
INSERT INTO DA.ISPITNIROK
VALUES (2021, 'Mar21', 'Mart 2021', '03/01/2022', '03/11/2022');
if (waitForLock("INSERT")) {
continue;
}
checkSQL("Insert");
if (SQLCODE == 0) {
break;
}
};
EXEC SQL SET CURRENT LOCK TIMEOUT NULL;
checkSQL("Set timeout");
EXEC SQL COMMIT;
checkSQL("Potvrdjivanje izmena");
EXEC SQL CONNECT RESET;
checkSQL("Connect reset");
return 0;
}
Datoteka: vezbe/primeri/poglavlje_6/zadatak_6_5/updateIspitniRok.sqc
:
#include <stdio.h>
#include <stdlib.h>
EXEC SQL INCLUDE SQLCA;
void checkSQL(const char *str) {
if(SQLCODE < 0) {
fprintf(stderr, "Greska %d: %s\n", SQLCODE, str);
EXEC SQL ROLLBACK;
EXEC SQL CONNECT RESET;
exit(EXIT_FAILURE);
}
}
int waitForLock(const char *code_hint) {
if (-913 <= SQLCODE && SQLCODE <= -911) {
printf("[%s] Objekat je zakljucan od strane druge transakcije. "
"Sacekati neko vreme...\n", code_hint);
EXEC SQL ROLLBACK;
checkSQL("Rollback");
return 1;
}
return 0;
}
int main() {
EXEC SQL CONNECT TO stud2020 USER student USING abcdef;
checkSQL("Connect");
EXEC SQL SET CURRENT LOCK TIMEOUT 5;
checkSQL("Set timeout");
for (;;) {
EXEC SQL
UPDATE DA.ISPITNIROK
SET DATKRAJA = DATKRAJA + 3 DAYS
WHERE SKGODINA = 2021;
if (waitForLock("UPDATE")) {
continue;
}
checkSQL("Insert");
if (SQLCODE == 0) {
break;
}
};
EXEC SQL SET CURRENT LOCK TIMEOUT NULL;
checkSQL("Set timeout");
EXEC SQL COMMIT;
checkSQL("Potvrdjivanje izmena");
EXEC SQL CONNECT RESET;
checkSQL("Connect reset");
return 0;
}
Zadatak 6.6: Napisati:
uncommittedRead
koji dva puta ispisuje informacije o godini, oznaci, nazivu i periodu prijavljivanja za svaki ispitni rok u 2021. godini. Dozvoljeno je da se prilikom drugog ispisivanja pojave novi redovi, da budu vidljive izmene pročitanih redova, bilo one potvrđene ili ne.insertIspitniRok
koji unosi novi ispitni rok za mesec mart u 2021. godini. Postaviti istek vremena za zahtevanje katanaca na 5 sekundi. Zahtevati od korisnika eksplicitno potvrđivanje izmena nakon unosa.updateIspitniRok
koji za svaki ispitni rok u 2021. godini produžava period prijavljivanja za 3 dana. Postaviti istek vremena za zahtevanje katanaca na 5 sekundi. Zahtevati od korisnika eksplicitno potvrđivanje izmena nakon ažuriranja.Rešenje: Kao i u prethodnom zadatku, napravljena je Makefile
koja služi za pozivanje alata make
na već opisan način.
Pokrenuti program uncommittedRead
i započeti prvo ispisivanje. U toku ispisivanja ili nakon njega, pokrenuti program insertIspitniRok
, ali ne potvrditi izmene još uvek. Zatim, nastaviti sa daljim ispisivanjem ispitnih rokova u programu uncommittedRead
. Uveriti se da program insertIspitniRok
može da izvrši unos novog ispitnog roka tokom rada programa uncommittedRead
i da se novi rok vidi prilikom drugog ispisivanja, bez obzira što izmene u programu insertIspitniRok
nisu potvrđene. Zatim, ponovo pokrenuti program readStability
(nakon ponovnog pripremanja baze podataka pozivom make db
) i uraditi isti postupak sa programom updateIspitniRok
umesto insertIspitniRok
. Uveriti se će i ovoga puta program uncommittedRead
videti nepotvrđene izmene od strane programa updateIspitniRok
, slično kao i u slučaju programa insertIspitniRok
. Ovo je ponašanje koje se neće javiti u slučaju nivoa izolovanosti CS ili strožijim od njega.
Datoteka: vezbe/primeri/poglavlje_6/zadatak_6_6/uncommittedRead.sqc
:
#include <stdio.h>
#include <stdlib.h>
EXEC SQL INCLUDE SQLCA;
EXEC SQL BEGIN DECLARE SECTION;
short hSchoolYear;
char hFinalsLabel[21],
hFinalsName[31],
hFinalsStartDate[11],
hFinalsEndDate[11];
EXEC SQL END DECLARE SECTION;
void checkSQL(const char *str) {
if(SQLCODE < 0) {
fprintf(stderr, "Greska %d: %s\n", SQLCODE, str);
EXEC SQL ROLLBACK;
EXEC SQL CONNECT RESET;
exit(EXIT_FAILURE);
}
}
int main() {
EXEC SQL CONNECT TO stud2020 USER student USING abcdef;
checkSQL("Connect");
EXEC SQL
DECLARE cFinals CURSOR FOR
SELECT SKGODINA,
OZNAKAROKA,
NAZIV,
DATPOCETKA,
DATKRAJA
FROM DA.ISPITNIROK
WHERE SKGODINA = 2021
FOR READ ONLY
WITH UR;
checkSQL("Declare cursor");
int i = 1;
for(; i<3; ++i)
{
printf("\nCitanje rezultata %d. put. Pritisnite ENTER za pocetak.\n", i);
getchar();
EXEC SQL OPEN cFinals;
checkSQL("Open");
printf("\n---------------------------------------------------------\n");
for(;;)
{
EXEC SQL
FETCH cFinals
INTO :hSchoolYear,
:hFinalsLabel,
:hFinalsName,
:hFinalsStartDate,
:hFinalsEndDate;
checkSQL("Fetch");
if(SQLCODE == 100) {
break;
}
printf("%4.4d %-10.10s %-20.20s %-10.10s %-10.10s\n", hSchoolYear, hFinalsLabel, hFinalsName, hFinalsStartDate, hFinalsEndDate);
}
printf("\n---------------------------------------------------------\n");
EXEC SQL CLOSE cFinals;
checkSQL("Close");
}
EXEC SQL COMMIT;
checkSQL("Potvrdjivanje izmena");
EXEC SQL CONNECT RESET;
checkSQL("Connect reset");
return 0;
}
Datoteka: vezbe/primeri/poglavlje_6/zadatak_6_6/insertIspitniRok.sqc
:
#include <stdio.h>
#include <stdlib.h>
EXEC SQL INCLUDE SQLCA;
void checkSQL(const char *str) {
if(SQLCODE < 0) {
fprintf(stderr, "Greska %d: %s\n", SQLCODE, str);
EXEC SQL ROLLBACK;
EXEC SQL CONNECT RESET;
exit(EXIT_FAILURE);
}
}
int waitForLock(const char *code_hint) {
if (-913 <= SQLCODE && SQLCODE <= -911) {
printf("[%s] Objekat je zakljucan od strane druge transakcije. "
"Sacekati neko vreme...\n", code_hint);
EXEC SQL ROLLBACK;
checkSQL("Rollback");
return 1;
}
return 0;
}
int main() {
EXEC SQL CONNECT TO stud2020 USER student USING abcdef;
checkSQL("Connect");
EXEC SQL SET CURRENT LOCK TIMEOUT 5;
checkSQL("Set timeout");
for (;;) {
EXEC SQL
INSERT INTO DA.ISPITNIROK
VALUES (2021, 'Mar21', 'Mart 2021', '03/01/2022', '03/11/2022');
if (waitForLock("INSERT")) {
continue;
}
checkSQL("Insert");
if (SQLCODE == 0) {
break;
}
};
EXEC SQL SET CURRENT LOCK TIMEOUT NULL;
checkSQL("Set timeout");
printf("\nPritisnuti enter za potvrdjivanje izmena: ");
getchar();
EXEC SQL COMMIT;
checkSQL("Potvrdjivanje izmena");
EXEC SQL CONNECT RESET;
checkSQL("Connect reset");
return 0;
}
Datoteka: vezbe/primeri/poglavlje_6/zadatak_6_6/updateIspitniRok.sqc
:
#include <stdio.h>
#include <stdlib.h>
EXEC SQL INCLUDE SQLCA;
void checkSQL(const char *str) {
if(SQLCODE < 0) {
fprintf(stderr, "Greska %d: %s\n", SQLCODE, str);
EXEC SQL ROLLBACK;
EXEC SQL CONNECT RESET;
exit(EXIT_FAILURE);
}
}
int waitForLock(const char *code_hint) {
if (-913 <= SQLCODE && SQLCODE <= -911) {
printf("[%s] Objekat je zakljucan od strane druge transakcije. "
"Sacekati neko vreme...\n", code_hint);
EXEC SQL ROLLBACK;
checkSQL("Rollback");
return 1;
}
return 0;
}
int main() {
EXEC SQL CONNECT TO stud2020 USER student USING abcdef;
checkSQL("Connect");
EXEC SQL SET CURRENT LOCK TIMEOUT 5;
checkSQL("Set timeout");
for (;;) {
EXEC SQL
UPDATE DA.ISPITNIROK
SET DATKRAJA = DATKRAJA + 3 DAYS
WHERE SKGODINA = 2021;
if (waitForLock("UPDATE")) {
continue;
}
checkSQL("Insert");
if (SQLCODE == 0) {
break;
}
};
EXEC SQL SET CURRENT LOCK TIMEOUT NULL;
checkSQL("Set timeout");
printf("\nPritisnuti enter za potvrdjivanje izmena: ");
getchar();
EXEC SQL COMMIT;
checkSQL("Potvrdjivanje izmena");
EXEC SQL CONNECT RESET;
checkSQL("Connect reset");
return 0;
}
Sledi još jedan zadatak koji ilustruje koncept konkurentnih transakcija u višekorisničkom okruženju. Za potrebe ovog zadatka je neophodno izvršiti naredne SQL naredbe:
CREATE TABLE DA.DRZAVA (
IDDRZAVE INTEGER NOT NULL PRIMARY KEY,
NAZIV VARCHAR(50) NOT NULL
);
CREATE TABLE DA.EKSKURZIJA (
INDEKS INTEGER NOT NULL,
IDDRZAVE INTEGER NOT NULL,
TRENUTAKPRIJAVE TIMESTAMP NOT NULL,
PRIMARY KEY (INDEKS),
FOREIGN KEY (INDEKS) REFERENCES DA.DOSIJE,
FOREIGN KEY (IDDRZAVE) REFERENCES DA.DRZAVA
);
INSERT INTO DA.DRZAVA
VALUES (1, 'Spanija'),
(2, 'Italija'),
(3, 'Grcka'),
(4, 'Nemacka'),
(5, 'Rusija'),
(6, 'Francuska');
Zadatak 6.7: Napisati C/SQL program koji od korisnika zahteva da unese identifikator studijskog programa sa osnovnih studija. Program na osnovu unetog podatka pronalazi naredne informacije o studentima sa datog studijskog programa: (1) broj indeksa, (2) ime, (3) prezime, (4) broj položenih ispita tog studenta, (5) broj položenih ESPB bodova, ali samo ukoliko student ima položeno bar 12 predmeta, ima skupljeno bar 120 bodova i ako se studentov indeks već ne nalazi u tabeli EKSKURZIJA
.
Za svakog pronađenog studenta, korisnik unosi ceo broj koji predstavlja jedan od identifikatora država iz tabele DRZAVA
ili 0 (pretpostaviti da je ispravan unos). Ukoliko korisnik unese identifikator, aplikacija unosi informacije u tabelu EKSKURZIJA
, a ukoliko unese 0, prelazi se na sledećeg studenta. Nakon svaka 3 uneta glasa, ponuditi korisniku mogućnost da prekine sa daljom obradom i napusti program, unošenjem odgovora 'da'
.
Napomene: Obrada jednog studenta predstavlja jednu transakciju. Proveravati greške koje se javljaju prilikom izvršavanja aplikacije u višekorisničkom okruženju. Postaviti istek vremena za zahtevanje katanaca na 5 sekundi. Voditi računa da aplikacija ne sme da vidi bilo kakve vrste izmena od strane drugih aplikacija.
Datoteka: vezbe/primeri/poglavlje_6/zadatak_6_7.sqc
:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
EXEC SQL INCLUDE SQLCA;
EXEC SQL BEGIN DECLARE SECTION;
sqlint32 hStudyProgramId;
sqlint32 hIndex;
char hName[26];
char hSurname[26];
sqlint32 hNumOfPassedExams;
sqlint32 hSumESPB;
sqlint32 hCountryId;
EXEC SQL END DECLARE SECTION;
void checkSQL(const char *str);
int waitForLock(const char *code_hint);
int main() {
EXEC SQL CONNECT TO stud2020 USER student USING abcdef;
checkSQL("CONNECT TO");
EXEC SQL SET CURRENT LOCK TIMEOUT 5;
checkSQL("CURRENT LOCK TIMEOUT 5");
printf("Unesite identifikator studijskog programa sa osnovnih studija: ");
scanf("%d", &hStudyProgramId);
EXEC SQL DECLARE cStudents CURSOR WITH HOLD FOR
SELECT D.INDEKS,
RTRIM(D.IME),
RTRIM(D.PREZIME),
COUNT(*) AS BROJ_POLOZENIH,
SUM(P.ESPB) AS BROJ_ESPB
FROM DA.DOSIJE D JOIN
DA.ISPIT I ON D.INDEKS = I.INDEKS JOIN
DA.PREDMET P ON I.IDPREDMETA = P.ID
WHERE D.IDPROGRAMA = :hStudyProgramId AND
I.OCENA > 5 AND
I.STATUS = 'o' AND
D.INDEKS NOT IN (
SELECT INDEKS
FROM DA.EKSKURZIJA
)
GROUP BY D.INDEKS,
D.IME,
D.PREZIME
HAVING COUNT(*) >= 12 AND
SUM(P.ESPB) >= 120
FOR READ ONLY
WITH RR;
checkSQL("DECLARE");
EXEC SQL OPEN cStudents;
checkSQL("OPEN");
unsigned numOfStudents = 0u;
for(;;) {
EXEC SQL FETCH cStudents INTO
:hIndex,
:hName,
:hSurname,
:hNumOfPassedExams,
:hSumESPB;
if (waitForLock("FETCH")) {
continue;
}
checkSQL("FETCH");
if (SQLCODE == 100) {
break;
}
printf("\n----------------------------------------------------------------------------------------------------------------------\n");
printf("Anketiram studenta: %-10.10s %-15.15s (%d) - broj polozenih ispita = %5.5d, broj polozenih espb = %5.5d",
hName, hSurname, hIndex, hNumOfPassedExams, hSumESPB);
printf("\n----------------------------------------------------------------------------------------------------------------------\n");
printf("\nUnesite identifikator drzave ili 0: ");
scanf("%d", &hCountryId);
if (!hCountryId) {
continue;
}
printf("Unosim Vas glas za drzavu sa identifikatorom %d...\n", hCountryId);
EXEC SQL
INSERT INTO DA.EKSKURZIJA
VALUES (:hIndex, :hCountryId, CURRENT_DATE);
if (waitForLock("INSERT INTO")) {
continue;
}
// Ako je neka druga aplikacija vec unela red za datog studenta...
if (SQLCODE != -803) {
checkSQL("INSERT INTO");
}
else {
// ...onda cemo pitati korisnika da li zeli da radi azuriranje glasa.
printf("Postoji glas za tekuceg studenta. Da li zelite da azurirate glas? [d/n] ");
char odgovor = getchar();
getchar();
if (odgovor == 'n') {
break;
}
EXEC SQL
UPDATE DA.EKSKURZIJA
SET IDDRZAVE = :hCountryId
WHERE INDEKS = :hIndex;
if (waitForLock("UPDATE")) {
continue;
}
checkSQL("UPDATE");
}
printf("Vas glas je uspesno zabelezen!\n");
EXEC SQL COMMIT;
checkSQL("COMMIT");
printf("\n********** END TRANSACTION **********\n");
++numOfStudents;
if (!(numOfStudents % 3)) {
printf("Da li zelite da zavrsite anketiranje? [da/ne] ");
char userResponse[3];
scanf("%s", userResponse);
if (!strcmp(userResponse, "da")) {
printf("Zavrsavam program!\n");
break;
}
}
}
EXEC SQL CLOSE cStudents;
checkSQL("CLOSE");
EXEC SQL SET CURRENT LOCK TIMEOUT NULL;
checkSQL("CURRENT LOCK TIMEOUT NULL");
EXEC SQL COMMIT;
checkSQL("COMMIT - kraj programa");
EXEC SQL CONNECT RESET;
checkSQL("CONNECT RESET");
return 0;
}
void checkSQL(const char *str) {
if(SQLCODE < 0) {
fprintf(stderr, "Greska %d: %s\n", SQLCODE, str);
EXEC SQL ROLLBACK;
EXEC SQL CONNECT RESET;
exit(EXIT_FAILURE);
}
}
int waitForLock(const char *code_hint) {
if (-913 <= SQLCODE && SQLCODE <= -911) {
printf("[%s] Objekat je zakljucan od strane druge transakcije. "
"Sacekati neko vreme...\n", code_hint);
EXEC SQL ROLLBACK;
checkSQL("Rollback");
EXEC SQL OPEN cStudents;
checkSQL("Open predmeti - obrada cekanja");
return 1;
}
return 0;
}
Programeri mogu sami da zatraže katance od SUBP ukoliko smatraju da im je to neophodno, ali sa ograničenjem da je na ovaj način jedino moguće zaključati tabelu, a ne redove, blokove, i druge objekte. SQL naredba LOCK TABLE
onemogućava konkurentnoj aplikaciji da koristi ili menja tabelu, u zavinosti od režima u kojem se tabela zaključava. Ovako dobijeni katanac se oslobađa kada se završi jedinica posla u kojem je naredba LOCK TABLE
izvršena, bilo operacijom potvrđivanja izmena ili njenim završavanjem.
Korisnik koji izvršava naredbu LOCK TABLE
mora da ima barem jednu od narednih privilegija da bi je uspešno izvršio:
SELECT
privilegiju nad tabelom koja se zaključava.CONTROL
privilegiju nad tabelom koja se zaključava.DATAACCESS
privilegiju.Sintaksa ove naredbe je data u nastavku:
LOCK TABLE <IME_TABELE> IN [SHARE|EXCLUSIVE] MODE
Ovom naredbom se traži odgovarajući katanac nad tabelom <IME_TABELE>
. U zavisnosti od odabranih vrednosti narednih opcija prilikom deklaracije složene SQL naredbe, ta naredba može imati različite varijante koji utiču na način izvršavanja:
Klauza SHARE
onemogućava konkurentnoj aplikaciji da izvrši bilo koju operaciju osim čitanja podataka iz tabele.
Kluza EXCLUSIVE
onemogućava konkurentnoj aplikaciji da izvrši bilo koju operaciju nad tabelom. Napomenimo da ovaj režim ne onemogućava da aplikacioni procesi izvršavaju naredbe čitanja podataka iz tabele ukoliko oni rade na nivou izolacije nepotvrđeno čitanje (UR).
Zadatak 6.6: Napisati C/SQL program shareMode
koji ispisuje identifikator, oznaku, naziv i broj ESPB bodova za svaki studijski program u bazi podataka. Omogućiti da ostale aplikacije ne mogu da menjaju ove podatke tokom ispisivanja podataka.
Napisati C/SQL program exclusiveMode
koji ispisuje identifikator, oznaku, naziv, broj semestara i broj ESPB bodova za svaki studijski program u bazi podataka. Omogućiti da ostale aplikacije ne mogu da menjaju ove podatke tokom ispisivanja podataka, kao ni da ih čitaju.
Rešenje: Isprobati sve kombinacije redosleda izvršavanja programa exclusiveMode
i shareMode
i uveriti se da jedino kombinacija (shareMode, shareMode)
može raditi konkurentno. Primetimo da u ovom zadatku nismo koristili obradu čekanja na katance niti smo postavili vreme za istek katanaca, što znači da će jedna aplikacija čekati sve dok druga ne završi sa radom.
Datoteka: vezbe/primeri/poglavlje_6/zadatak_6_8/shareMode.sqc
:
#include <stdio.h>
#include <stdlib.h>
EXEC SQL INCLUDE SQLCA;
EXEC SQL BEGIN DECLARE SECTION;
sqlint32 hCourseId;
char hCourseLabel[11];
char hCourseName[201];
short hCourseESPB;
EXEC SQL END DECLARE SECTION;
void checkSQL(const char *str) {
if(SQLCODE < 0) {
fprintf(stderr, "Greska %d: %s\n", SQLCODE, str);
EXEC SQL ROLLBACK;
EXEC SQL CONNECT RESET;
exit(EXIT_FAILURE);
}
}
int main() {
EXEC SQL CONNECT TO stud2020 USER student USING abcdef;
checkSQL("Connect");
// Zakljucavamo tabelu PREDMET u deljenom rezimu
EXEC SQL LOCK TABLE DA.PREDMET IN SHARE MODE;
checkSQL("Lock table");
EXEC SQL
DECLARE cCourses CURSOR FOR
SELECT ID,
OZNAKA,
NAZIV,
ESPB
FROM DA.PREDMET;
checkSQL("Declare cursor");
EXEC SQL OPEN cCourses;
checkSQL("Open cursor");
for(;;) {
EXEC SQL
FETCH cCourses
INTO :hCourseId,
:hCourseLabel,
:hCourseName,
:hCourseESPB;
checkSQL("Fetch cursor");
if (SQLCODE == 100)
{
break;
}
printf("%4.4d %-10.10s %-30.30s %d\n", hCourseId, hCourseLabel, hCourseName, hCourseESPB);
printf("Pritisnite ENTER za dalje citanje\n");
getchar();
}
EXEC SQL CLOSE cCourses;
checkSQL("Close cursor");
EXEC SQL COMMIT;
checkSQL("Potvrdjivanje izmena");
EXEC SQL CONNECT RESET;
checkSQL("Connect reset");
return 0;
}
Datoteka: vezbe/primeri/poglavlje_6/zadatak_6_8/exclusiveMode.sqc
:
#include <stdio.h>
#include <stdlib.h>
EXEC SQL INCLUDE SQLCA;
EXEC SQL BEGIN DECLARE SECTION;
sqlint32 hCourseId;
char hCourseLabel[11];
char hCourseName[201];
short hCourseESPB;
EXEC SQL END DECLARE SECTION;
void checkSQL(const char *str) {
if(SQLCODE < 0) {
fprintf(stderr, "Greska %d: %s\n", SQLCODE, str);
EXEC SQL ROLLBACK;
EXEC SQL CONNECT RESET;
exit(EXIT_FAILURE);
}
}
int main() {
EXEC SQL CONNECT TO stud2020 USER student USING abcdef;
checkSQL("Connect");
// Zakljucavamo tabelu PREDMET u privatnom rezimu
EXEC SQL LOCK TABLE DA.PREDMET IN EXCLUSIVE MODE;
checkSQL("Lock table");
EXEC SQL
DECLARE cCourses CURSOR FOR
SELECT ID,
OZNAKA,
NAZIV,
ESPB
FROM DA.PREDMET;
checkSQL("Declare cursor");
EXEC SQL OPEN cCourses;
checkSQL("Open cursor");
for(;;) {
EXEC SQL
FETCH cCourses
INTO :hCourseId,
:hCourseLabel,
:hCourseName,
:hCourseESPB;
checkSQL("Fetch cursor");
if (SQLCODE == 100)
{
break;
}
printf("%4.4d %-10.10s %-30.30s %d\n", hCourseId, hCourseLabel, hCourseName, hCourseESPB);
printf("Pritisnite ENTER za dalje citanje\n");
getchar();
}
EXEC SQL CLOSE cCourses;
checkSQL("Close cursor");
EXEC SQL COMMIT;
checkSQL("Potvrdjivanje izmena");
EXEC SQL CONNECT RESET;
checkSQL("Connect reset");
return 0;
}
Zadatak 6.9: Napisati C/SQL program koji za sve ispitne rokove pronalazi informacije o polaganjima za svaki položeni predmet u tom ispitnom roku i te podatke unosi u tabelu ISPITNIROKOVIPOLAGANJA
. Nakon jednog unosa podataka ispisati unete podatke odvojene zapetom. Nakon svih unosa podataka, potrebno je odgovarajućom SQL naredbom izračunati broj unetih redova u tabeli ISPITNIROKOVIPOLAGANJA
i ispisati rezultat na standardni izlaz. Napisati program tako da može da radi u višekorisničkom okruženju. Unos podataka za jedan ispitni rok i jedno polaganje predstavlja jednu transakciju. Postaviti istek vremena za zahtevanje katanaca na 5 sekundi.
Napraviti tabelu ISPITNIROKOVIPOLAGANJA
koja sadrži kolone sa podacima o:
Definisati primarni ključ na osnovu prvih 5 kolona i strane ključeve ka odgovarajućim tabelama.
Zadatak 6.10: Napisati C/SQL program koji za sve studente pronalazi informacije o studijskom programu na kojem studiraju i te podatke unosi u tabelu STUDIJSKIPROGRAMPSTUDENTI
. Nakon jednog unosa podataka ispisati unete podatke odvojene zapetom. Nakon svih unosa podataka, potrebno je odgovarajućom SQL naredbom izračunati broj unetih redova u tabeli STUDIJSKIPROGRAMPSTUDENTI
i ispisati rezultat na standardni izlaz. Napisati program tako da može da radi u višekorisničkom okruženju. Unos podataka za jednog studenta i jedan studijski program predstavlja jednu transakciju. Postaviti istek vremena za zahtevanje katanaca na 5 sekundi.
Napraviti tabelu STUDIJSKIPROGRAMPSTUDENTI
koja sadrži kolone sa podacima o:
'Polozeni su svi predmeti'
ukoliko je student ostvario sve potrebne poene, a prazna niska u suprotnomDefinisati primarni ključ na osnovu prve 2 kolone i strane ključeve ka odgovarajućim tabelama.
Zadatak 6.11: Napisati C/SQL program koji redom omogućava naredne funkcionalnosti:
PRAKSA
.Svako prijavljivanje predstavlja nezavisnu transakciju. Napisati program tako da radi ispravno u višekorisničkom okruženju. Postaviti istek vremena za zahtevanje katanaca na 5 sekundi.
Potrebno je u bazi vstud kreirati naredbu koja pravi tabelu PRAKSA
koja ima kolone:
INDEKS
- indeks studenta;IDPREDMETA
- identifikator predmeta koji student prijavljuje.Definisati primarni ključ u tabeli PRAKSA
i strane ključeve nad tabelama DOSIJE
i PREDMET
. U ovoj tabeli se čuvaju podaci o prijavljenim praksama. Jedan student može potencijalno prijaviti više različitih predmeta, a i isti predmet može prijaviti više različitih studenata.
Obratiti pažnju da se ne radi o WITH
klauzi za kreiranje privremenih tabela u SELECT
naredbi, već da se ova WITH
klauza nekad naziva i isolation-clause
. ↩