Keletas teiginiu:
1. Serializable visada bus letesnis nei kiti isolation leveliai del vienos
paprastos priezasties - DBVS turi atlikti papildomus veiksmus tam, kad
serializuoti transakcijas. O papildomi veiksmai reikalauja laiko, galbut
labai labai mazai, bet visvien jie jo prideda, ne atima.
2. Zodziai serializable ir performance yra antonimai, ypac high concurency
aplinkoje.
3. Zodziai deadlock ir performance yra antonimai, cia net nematau reikalo
kazka bandyti spresti deadlocku pagalba, tai savizudybe.
4. Serializable apsaugo nuo phantomu. Phantomai is principo neimanomi
dirbant su vienu irasu. Phantomas gali atsirasti tik tada, kai tas pats
selectas kartojamas vienoj transakcijoj bent 2 kartus, + to selecto atrankos
kriterijai leidzia jam grazinti daugiau nei viena irasa (select * from tbl
where pk_value = :constant, negali grazinti daugiau nei vieno iraso).
Kadangi phantomas cia niekaip negali atsirasti, vadinasi, norint pagerint
greitaveika(!), nera reikalo naudoti serial transakciju.
Prie viso to anot MySQL dokumentacijos (nepykit jei nepataikiau i versija,
ziurejau pirma pasitaikiusia):
http://dev.mysql.com/doc/refman/5.6/en/innodb-transaction-model.html#innodb-lock-modes
cia ziuriu apacioj i lentele lock type compatibility matrix, noriu atkreipti
demesi i tai, kad S ir S kombinacija yra compatible.
http://dev.mysql.com/doc/refman/5.6/en/innodb-transaction-model.html#innodb-locks-set
cia skaitau SELECT ... FROM is a consistent read, reading a snapshot of the
database and setting no locks unless the transaction isolation level is set
to SERIALIZABLE. For SERIALIZABLE level, the search sets shared next-key
locks on the index records it encounters. Esme tame, kad "sets shared(!)
next-key locks", kas leidzia ta pati padaryti kitai serial transakcijai, nes
S ir S pora yra compatible (zinoma as darau prielaida, kad konkreciam
vapyzdy id yra pirminis raktas) t.y. 2 transakcijos
begin transaction
select * from tbl where id = 5
delete from tbl where id = 5
end transaction
tarpusavyje nekonfliktuoja tol, kol nei viena is ju nepasieke delete
sakinio. Konfliktas atsiranti tik tada, kai bent viena transakcija sukurs X
locka. Taigi, potencialiai visos transakcijos po pirmos, kurios pataike
prasideti tarp pirmosios transakcijos begin transaction ir X locko sukurimo,
is esmes yra siuksles, nereikalingai apkraunancios sistemos darba, nes jos
ivykdys delete tik tuo atveju, jei ankstesne transakcija buvo abortinta.
Prie viso to, takrim pirmoji transakcija jau sukure X locka, bet uzklausimu
skaicius nesustoja, sistemai toliau kuriami uzklausimai su naujomis tokiomis
paciomis transakcijomis. Sios naujosios transakcijos, negali sukurti ir S
locko, tam, kad ivykdyti selecta, nes pirmoji yra sukurusi X locka tam
paciam irasui (vel ziuriu lock type compatibility matrix, matau X ir S pora
yra conflicting). Kaip MySQL elgsis tokiu atveju as nzn, reikia ziureti, bet
variantai, kuriuos dabar galiu sugalvoti yra tokie:
1. Restartuoti naujausias transakcijas rysium su tuo, kad irasas, kuriam jos
negalejo sukurti S locku, nebeegzistuoja (restartuoti=rollback+pradeti nuo
pradziu).
2. Grazinti exceptiona row does not exist ar pan.
3. Abortinti transakcija.
Noreciau atkreipti demesi, kad bet kuris is situ variantu reikalaus
papildomu veiksmu is DBVS, kuri savo algoritmais tures atpazinti tokias
situacijas t.y. dar papildomai apkraus sistema. Taigi, visos transakcijos,
kurios yra sukurtos tuo metu kai yra vykdoma pirmoji transakcija yra tik
papildoma apkrovima sistemai, kuri realiai neduoda jokio rezultato, nes
neatlieka jokiu veiksmu. Tiketis greitaveikos pagerejimo galima tik is MySQL
vidiniu resursu, t.y. darant prielaida, kad InnoDB susitvarkys greiciau su
transakcijomis (iskaitanta tas, kurios sukasi be jokio rezultato), palyginus
su ISAM write lock komanda. Man tai labiau panasu yra lavono reanimavima,
negu sprendima t.y. takrim ISAM pradeda stabdyti nuo 30 konkurentiniu
vartotoju, na gerai InnoDB prades stabdyti nuo 100. Esme tame, kad pasiekus
ta 100 visvien bus sakes. Tada lieka tik pasiulymas - nusipirkit greitesni
proca. Man asmeniskai butu zema siulyti nusipirkti greitesni proca,
nepasiulius kitu alternatyvu :)
Neturiu po ranka MySQL, galbut cia kasnors galetu pagelbeti ir atlikti tai,
ka as pabandziau su Oracle. Tai labai paprasta ir uzima gerokai maziau laiko
negu man parasyti sita posta :)
Stai pavyzdiai is Oracle
create table TST
(
ID NUMBER not null,
NOTE VARCHAR2(2000),
constraint TST_PK primary key (ID)
)
insert into TST (ID, NOTE)
values (1, null);
insert into TST (ID, NOTE)
values (2, null);
insert into TST (ID, NOTE)
values (3, null);
insert into TST (ID, NOTE)
values (4, null);
insert into TST (ID, NOTE)
values (5, null);
commit;
susikuriau 3 sesijas (3 tam, kad patikrinti ar nebus deadlocku) su vienodom
transakcijom ir paleidau jas vienu metu
READ COMMITED atveju
session 1
SQL> set serveroutput on;
SQL> declare
2 i number;
3 begin
4 set transaction isolation level read committed;
5 select id into i from tst where id = (select min(id) from tst) ;
6 dbms_output.put_line('id=' || i);
7 delete tst where id = i;
8 dbms_lock.sleep(10);
9 dbms_output.put_line(sql%rowcount || ' row(s) deleted');
10 commit;
11 end;
12 /
id=1
1 row(s) deleted
PL/SQL procedure successfully completed.
session 2
SQL> set serveroutput on;
SQL> declare
..... tas pats kas auksciau
id=1
0 row(s) deleted
session 3
SQL> set serveroutput on;
SQL> declare
..... tas pats kas auksciau
id=1
0 row(s) deleted
t.y. READ COMMITED atveju oracle tiesiog abortina 2 ir 3 transakcijas
SERIALIZABLE atvejis
session 1
SQL> ed
Wrote file afiedt.buf
1 declare
2 i number;
3 begin
4 set transaction isolation level serializable;
5 select id into i from tst where id = (select min(id) from tst) ;
6 dbms_output.put_line('id=' || i);
7 delete tst where id = i;
8 dbms_lock.sleep(10);
9 dbms_output.put_line(sql%rowcount || ' row(s) deleted');
10 commit;
11* end;
SQL> /
id=2
1 row(s) deleted
session 2
SQL> declare
... tas pats kas auksciau
id=2
declare
*
ERROR at line 1:
ORA-08177: can't serialize access for this transaction
ORA-06512: at line 7
session 3
SQL> declare
... tas pats kas auksciau
id=2
declare
*
ERROR at line 1:
ORA-08177: can't serialize access for this transaction
ORA-06512: at line 7
jokiu deadlocku, serialines transakcijos tiesiog taip neveikia (bent jau
Oracle atveju). Jei MySQL grazins taip pat exception, bus katastrofa, nes,
speju, exceptionais pasibaigusiu transakciju bus daugiau negu normaliai
pasibaigusiu...
Tam, kad pasiketi FIFO principa, reikia ne isolation level keisti, o
selectui deti FOR UPDATE (ka turi ir MySQL), tam, kad sis selectas sukurtu X
locka, vietoj S locko
FOR UPDATE atvejis
session 1
SQL> ed
Wrote file afiedt.buf
1 declare
2 i number;
3 begin
4 select id into i from tst where id = (select min(id) from tst) FOR
UPDATE;
5 dbms_output.put_line('id=' || i);
6 delete tst where id = i;
7 dbms_lock.sleep(10);
8 dbms_output.put_line(sql%rowcount || ' row(s) deleted');
9 commit;
10* end;
11 /
id=3
1 row(s) deleted
PL/SQL procedure successfully completed.
session 2
SQL> declare
.... tas pats kas auksciau
id=4
1 row(s) deleted
PL/SQL procedure successfully completed.
session 3
SQL> declare
.... tas pats kas auksciau
id=5
1 row(s) deleted
PL/SQL procedure successfully completed.
Va dabar viskas buvo ivykdyta is eiles, kadangi kiekviena sekanti
transakcija prasidejo tik po to, kai baigesi ankstesnioji. Visos 3 ivyko
leciau nei pirmi 2 atvejai, nes buvo vykdomos grieztai is eiles.
Tiesiog siuo konkreciu atveju su tokiu select for update, nera jokio
skirtumo ar rakinti viena irasa ar visa lenta, greitaveika bus ta pati...