Tātad mēs apskatījām jautājumus, kas saistīti ar , un veicām atkāpi par . Un visbeidzot esam nonākuši pie paša interesantākā — pie rindu versijām.
Virsraksts
Kā jau teicām, katra rinda var vienlaikus atrasties datu bāzē vairākās versijās. Vienu versiju no otras ir jāspēj atšķirt. Šim nolūkam katrai versijai ir divi atzīmes, kas nosaka šīs versijas „aktivitātes” laiku (xmin un xmax). Pēdiņās — jo izmanto ne laiku kā tādu, bet īpašu pieaugošu skaitītāju. Un šis skaitītājs ir darījuma numurs.
(Kā parasti, patiesībā viss ir sarežģītāk: darījumu numurs nevar pastāvīgi pieaugt ierobežotās skaitītāja precizitātes dēļ. Bet šos detaļas mēs apskatīsim detalizēti, kad nonāksim pie sasalšanas.)
Kad rinda tiek izveidota, xmin vērtība tiek iestatīta uz darījuma numuru, kas izpildījis INSERT komandu, bet xmax netiek aizpildīta.
Kad rinda tiek dzēsta, aktuālās versijas xmax vērtība tiek atzīmēta ar darījuma numuru, kas izpildījis DELETE.
Kad rinda tiek mainīta ar UPDATE komandu, faktiski izpildās divas operācijas: DELETE un INSERT. Aktuālajā versijā rindas xmax vērtība ir darījuma numurs, kas izpildījis UPDATE. Pēc tam tiek izveidota jauna versija tās pašas rindas; xmin vērtība tai atbilst iepriekšējās versijas xmax vērtībai.
Lauki xmin un xmax ietilpst rindas versijas galvā. Turklāt galvā ietilpst arī citi lauki, piemēram:
- infomask — virknes bitu, kas nosaka šīs versijas īpašības. Tās ir daudz; galvenās no tām pakāpeniski apskatīsim.
- ctid — saite uz nākamo, jaunāko šīs pašas rindas versiju. Visjaunākajai, aktuālajai, rindas versijai ctid norāda uz šo pašu versiju. Numurs ir formas (x,y), kur x — lapas numurs, y — rādītāja secīgais numurs masīvā.
- nenoteikto vērtību bitu karte — iezīmē tos kolonnas šīs versijas, kas satur nenoteiktu vērtību (NULL). NULL nav viens no parastajiem datu tipu vērtībām, tāpēc šim rādītājam ir jāuzglabā atsevišķi.
Rezultātā galva iznāk diezgan liela — vismaz 23 baitus uz katru rindas versiju, bet parasti vairāk dēļ NULL bitu kartes. Ja tabula ir "šaura" (t.i., satur maz kolonnas), noslodzi var būt vairāk nekā noderīgā informācija.
Ievietošana
Apskatīsim sīkāk, kā zemas līmeņa darbības tiek veiktas ar virkņu apstrādi, sākot ar ievietošanu.
Eksperimentiem izveidosim jaunu tabulu ar divām kolonnām un indeksu uz vienu no tām:
=> IZVEIDOT TABULU t(
id serial,
s text
);
=> IZVEIDOT INDEx ON t(s);
Ievietosim vienu rindu, sākot ar darījuma uzsākšanu.
=> SĀKT;
=> IEVADĪT t(s) VĒRTĪBĀS ('FOO');
Lūk, mūsu pašreizējā darījuma numurs:
=> IZVĒLĒTIES txid_current();
txid_current
--------------
3664
(1 rinda)
Paskatīsimies lapas saturu. Funkcija heap_page_items no pageinspect paplašinājuma ļauj iegūt informāciju par rindu rādītājiem un versijām:
=> IZVĒLĒTIES * NO heap_page_items(get_raw_page('t',0)) gx
-[ REKORDS 1 ]-------------------
lp | 1
lp_off | 8160
lp_flags | 1
lp_len | 32
t_xmin | 3664
t_xmax | 0
t_field3 | 0
t_ctid | (0,1)
t_infomask2 | 2
t_infomask | 2050
t_hoff | 24
t_bits |
t_oid |
t_data | x0100000009464f4f
Pievērsīsim uzmanību tam, ka vārds heap (kaudze) PostgreSQL tiek izmantots tabulu apzīmēšanai. Tas vēlreiz ir dīvains terminoloģijas lietojums — kaudze ir zināma , kurai nav nekā kopīga ar tabulu. Šeit šis vārds tiek lietots kā "viss sakrauts kopā", atšķirībā no sakārtotajiem indeksiem.
Funkcija parāda datus "kā ir", formātā, kas ir grūti uztverams. Lai saprastu, mēs atstāsim tikai daļu informācijas un izskaidrosim to:
=> IZVĒLĒTIES '(0,'||lp||')' KĀ ctid,
CASE lp_flags
WHEN 0 THEN 'neizmantots'
WHEN 1 THEN 'normāls'
WHEN 2 THEN 'pārorientēt uz '||lp_off
WHEN 3 THEN 'miris'
END KĀ stāvoklis,
t_xmin kā xmin,
t_xmax kā xmax,
(t_infomask & 256) > 0 KĀ xmin_commited,
(t_infomask & 512) > 0 KĀ xmin_aborted,
(t_infomask & 1024) > 0 KĀ xmax_commited,
(t_infomask & 2048) > 0 KĀ xmax_aborted,
t_ctid
NO heap_page_items(get_raw_page('t',0)) gx
-[ REKORDS 1 ]-+-------
ctid | (0,1)
state | normāls
xmin | 3664
xmax | 0
xmin_commited | f
xmin_aborted | f
xmax_commited | f
xmax_aborted | t
t_ctid | (0,1)
Lūk, ko mēs izdarījām:
- Pievienojām pie rādītāja numura nulli, lai to izveidotu tādā pašā veidā kā t_ctid: (lapas numurs, rādītāja numurs).
- Izskaidrojām rādītāja lp_flags stāvokli. Šeit tas ir "normāls" — tas nozīmē, ka atsauce patiešām norāda uz rindas versiju. Citus vērtības aplūkosim vēlāk.
- No visiem informatīvajiem bitiem pagaidām izdalījām tikai divus pārus. Biti xmin_committed un xmin_aborted norāda, vai darījums ar numuru xmin ir apstiprināts (atcelts). Divi līdzīgi biti attiecas uz darījumu ar numuru xmax.
Ko mēs redzam? Ievietojot rindu tabulā, parādīsies rādītājs ar numuru 1, kas norāda uz pirmo un vienīgo rindas versiju.
Rindas versijā lauks xmin ir aizpildīts ar pašreizējās transakcijas numuru. Transakcija joprojām ir aktīva, tāpēc abi bitu xmin_committed un xmin_aborted nav iestatīti.
Lauks ctid rindas versijā norāda uz to pašu rindas. Tas nozīmē, ka jaunākas versijas nav.
Lauks xmax ir aizpildīts ar fiktīvu numuru 0, jo šī rindas versija netika dzēsta un ir aktuāla. Transakcijas nepievērš uzmanību šim numuram, jo ir iestatīts bits xmax_aborted.
Veiksim vēl vienu soli lasāmības uzlabošanai, pievienojot informācijas bitus transakciju numuriem. Un izveidosim funkciju, jo šis pieprasījums mums vēl daudzkārt noderēs:
=> CREATE FUNCTION heap_page(relname text, pageno integer)
RETURNS TABLE(ctid tid, state text, xmin text, xmax text, t_ctid tid)
AS $$
SELECT (pageno,lp)::text::tid AS ctid,
CASE lp_flags
WHEN 0 THEN 'neizmantots'
WHEN 1 THEN 'parasts'
WHEN 2 THEN 'pāradresēt uz '||lp_off
WHEN 3 THEN 'miris'
END AS state,
t_xmin || CASE
WHEN (t_infomask & 256) > 0 THEN ' (c)'
WHEN (t_infomask & 512) > 0 THEN ' (a)'
ELSE ''
END AS xmin,
t_xmax || CASE
WHEN (t_infomask & 1024) > 0 THEN ' (c)'
WHEN (t_infomask & 2048) > 0 THEN ' (a)'
ELSE ''
END AS xmax,
t_ctid
FROM heap_page_items(get_raw_page(relname,pageno))
ORDER BY lp;
$$ LANGUAGE SQL;
Šādā veidā ir ievērojami saprotamāks, kas notiek rindas versijas galvenē:
=> SELECT * FROM heap_page('t',0);
ctid | state | xmin | xmax | t_ctid
-------+--------+------+-------+--------
(0,1) | parasts | 3664 | 0 (a) | (0,1)
(1 rinda)
Līdzīgu, bet daudz mazāk detalizētu informāciju var iegūt arī no pašas tabulas, izmantojot pseido kolonnas xmin un xmax:
=> SELECT xmin, xmax, * FROM t;
xmin | xmax | id | s
------+------+----+-----
3664 | 0 | 1 | FOO
(1 rinda)
Fiksācija
Veiksmīgas transakcijas pabeigšanas gadījumā ir jāpieraksta tās statuss — jāatzīmē, ka tā ir fiksēta. Tam tiek izmantota struktūra, kas saucas XACT (un līdz versijai 10 tā tika saukta par CLOG (commit log), un šis nosaukums joprojām var parādīties dažādās vietās).
XACT — nav sistēmas kataloga tabula; tās ir failus PGDATA/pg_xact direktorijā. Tajos katrai transakcijai ir paredzēti divi biti: committed un aborted — tieši tāpat kā rindas versijas galvenē. Šī informācija ir sadalīta vairākos failos tikai ērtības labad, mēs vēl pievērsīsimies šai tēmai, kad apspriedīsim sasalšanu. Un darbs ar šiem failiem tiek veikts lapu veidā, tāpat kā ar visiem citiem.
Tātad, fiksējot darījumu XACT, tiek iestatīts bits committed šim darījumam. Un tas ir viss, kas notiek fiksācijas laikā (patiesībā mēs vēl nerunājam par priekšrakstu žurnālu).
Kad kāds cits darījums piekļūs tabulā esošajai lapai, uz kuru mēs tikko skatījāmies, tam būs jāatbild uz dažiem jautājumiem.
- Vai darījums xmin ir noslēdzies? Ja nē, radītā rindu versija nedrīkst būt redzama.
Šāda pārbaude tiek veikta, pārskatot vēl vienu struktūru, kas atrodas instancē kopējā atmiņā un tiek saukta par ProcArray. Tajā ir visu aktīvo procesu saraksts, un katram ir norādīts tā pašreizējā (aktīvā) darījuma numurs. - Ja tā ir noslēdzies, tad kā — ar fiksāciju vai atcelšanu? Ja ar atcelšanu, tad rindu versija arī nedrīkst būt redzama.
Tāpēc ir vajadzīgs XACT. Tomēr, lai gan pēdējās XACT lapas tiek saglabātas buferos operatīvajā atmiņā, katru reizi pārbaudīt XACT ir neizdevīgi. Tāpēc reizi noskaidrota darījuma statuss tiek ierakstīts bitos xmin_committed un xmin_aborted rindu versijā. Ja viens no šiem bitiem ir iestatīts, tad darījuma xmin stāvoklis tiek uzskatīts par zināmu, un nākamajam darījumam vairs nebūs jāpiekļūst XACT.
Kāpēc šie biti netiek iestatīti pašā darījumā, kas veic ievadi? Kad notiek ievade, darījums vēl nezina, vai tas noslēgsies veiksmīgi. Un fiksācijas brīdī vairs nav skaidrs, kuras tieši rindas ir mainītas kurās lapās. Šādu lapu var būt daudz, un to atcerēšanās ir neizdevīga. Turklāt daļa lapu var tikt izņemta no buferu kešatmiņas diskā; to atkārtota lasīšana, lai mainītu bitus, būtiski palēninātu fiksāciju.
Ekonomijas otra puse ir tā, ka pēc izmaiņām jebkuram darījumam (pat ja tas veic vienkāršu lasīšanu — SELECT) var sākt mainīt datu lapas buferu kešatmiņā.
Tātad, fiksēsim izmaiņas.
=> COMMIT;
Lapas saturā nekas nav mainījies (bet mēs zinām, ka darījuma statuss jau ir ierakstīts XACT):
=> SELECT * FROM heap_page('t',0);
ctid | state | xmin | xmax | t_ctid
-------+--------+------+-------+--------
(0,1) | parasts | 3664 | 0 (a) | (0,1)
(1 rinda)
Tagad pirmais darījums, kas piekļuva lapai, noteiks darījuma xmin statusu un ierakstīs to informācijas bitos:
=> SELECT * FROM t;
id | s
----+-----
1 | FOO
(1 row)
=> SELECT * FROM heap_page('t',0);
ctid | state | xmin | xmax | t_ctid
-------+--------+----------+-------+--------
(0,1) | normal | 3664 (c) | 0 (a) | (0,1)
(1 row)
Dzēst
Kad rindiņa tiek dzēsta laukā xmax, aktuālajā versijā tiek ierakstīts pašreizējās dzēsto darījumu numurs, un bits xmax_aborted tiek atiestatīts.
Vēršam uzmanību, ka noteiktā vērtība xmax, kas atbilst aktīvam darījumam, kalpo kā rindiņas bloķētājs. Ja cits darījums mēģina atjaunināt vai dzēst šo rindiņu, tam būs jāgaida, kamēr pabeigsies darījums xmax. Par bloķēšanu detalizētāk runāsim vēlāk. Pag пока atzīmēsim, ka rindiņu bloķējumu skaits nav ierobežots. Tie neaizņem vietu operatīvajā atmiņā, un sistēmas veiktspēja necietīs no to skaita. Patiesībā 'garajiem' darījumiem ir arī citi trūkumi, bet par to runāsim vēlāk.
Dzēst rindiņu.
=> SĀKT;
=> DZĒST NO t;
=> IZVĒLĒT txid_current();
txid_current
--------------
3665
(1 rinda)
Redzam, ka darījuma numurs ierakstījies laukā xmax, bet informācijas biti nav iestatīti:
=> SELECT * FROM heap_page('t',0);
ctid | state | xmin | xmax | t_ctid
-------+--------+----------+------+--------
(0,1) | normāls | 3664 (c) | 3665 | (0,1)
(1 rinda)
Atcelšana
Atcelšanas darbība notiek līdzīgi apstiprināšanai, tikai XACT darījumam tiek iestatīts bits aborted. Atcelšana tiek veikta tikpat ātri, kā arī apstiprināšana. Lai arī komanda saucas ROLLBACK, izmaiņu atgriešana nenotiek: viss, ko darījums paspēja mainīt datu lapās, paliek nemainīgs.
=> ROLLBACK;
=> IZVĒLĒT * NO heap_page('t',0);
ctid | state | xmin | xmax | t_ctid
-------+--------+----------+------+--------
(0,1) | normāls | 3664 (c) | 3665 | (0,1)
(1 rinda)
Atsaucoties uz lapu, tiks pārbaudīts statuss, un rindiņas versijā tiks iestatīts bits norādei xmax_aborted. Pats numurs xmax paliek lapā, bet neviens uz to vairs neskatīsies.
=> SELECT * FROM t;
id | s
----+-----
1 | FOO
(1 row)
=> SELECT * FROM heap_page('t',0);
ctid | state | xmin | xmax | t_ctid
-------+--------+----------+----------+--------
(0,1) | normāls | 3664 (c) | 3665 (a) | (0,1)
(1 rinda)
Atjauninājums
Atjaunināšana darbojas tā, it kā vispirms tiktu veikta esošās rindiņas dzēšana, pēc tam jaunas ielāde.
=> SĀKT;
=> ATJAUNOT t IESTATĪT s = 'BAR';
=> IZVĒLĒT txid_current();
txid_current
--------------
3666
(1 rinda)
Vaicājums atgriež vienu rindiņu (jauno versiju):
=> SELECT * FROM t;
id | s
----+-----
1 | BAR
(1 rinda)
Bet lapā mēs redzam abas versijas:
=> SELECT * FROM heap_page('t',0);
ctid | state | xmin | xmax | t_ctid
-------+--------+----------+-------+--------
(0,1) | normāls | 3664 (c) | 3666 | (0,2)
(0,2) | normāls | 3666 | 0 (a) | (0,2)
(2 rindas)
Dzēstā versija ir atzīmēta ar pašreizējā darījuma numuru laukā xmax. Turklāt šī vērtība ir ierakstīta virs vecās, jo iepriekšējais darījums tika atcelts. Un bits xmax_aborted ir atiestatīts, jo pašreizējā darījuma statuss vēl nav zināms.
Pirmā rindiņas versija tagad norāda uz otro (lauka t_ctid), kā uz jaunāku.
Indeksu lapā parādās otrais rādītājs un otra rinda, kas atsaucas uz otro versiju tabulas lapā.
Tāpat kā dzēšanas gadījumā, xmax vērtība pirmajā versijā rindas kalpo kā norāde, ka rinda ir bloķēta.
Nu, noslēgsim transakciju.
=> COMMIT;
Indeksi
Līdz šim mēs runājām tikai par tabulas lapām. Bet kas notiek indeksos?
Informācija indeksu lapās ļoti atkarīga no konkrētā indeksu veida. Pat tam pašam indeksu veidam ir dažādi lapu veidi. Piemēram, B-koka indeksam ir lapas ar metadatiem un 'parastas' lapas.
Tomēr parasti lapā ir masīvs rādītāju uz rindām un pašām rindām (tāpat kā tabulas lapā). Turklāt lapas beigās tiek atvēlēta vieta speciālajiem datiem.
Rindām indeksos arī var būt ļoti atšķirīga struktūra atkarībā no indeksu veida. Piemēram, B-koka lapās esošās rindas satur indeksēšanas atslēgas vērtību un norādi (ctid) uz attiecīgo tabulas rindu. Vispārīgā gadījumā indekss var būt veidots pavisam citādi.
Visnozīmīgākais ir tas, ka jebkuras normas indeksos rindu versijas nepastāv. Vai arī var uzskatīt, ka katra rinda ir attēlota tieši ar vienu versiju. Citiem vārdiem sakot, indeksu rindu galvenes nav lauku xmin un xmax. Var uzskatīt, ka rādītāji no indeksa ved uz visām tabulas rindu versijām — tādēļ izprast, kuru versiju redzēs transakcija, var tikai ieskatoties tabulā. (Kā parasti, tas nav viss patiesības stāsts. Dažos gadījumos redzamības karte ļauj optimizēt procesu, bet par to sīkāk vēlāk.)
Tajā pašā laikā indeksu lapā atklājam rādītājus uz abām versijām, gan aktuālo, gan veco:
=> SELECT itemoffset, ctid FROM bt_page_items('t_s_idx',1);
itemoffset | ctid
------------+-------
1 | (0,2)
2 | (0,1)
(2 rindas)
Virtuālās transakcijas
Praxe PostgreSQL izmanto optimizāciju, kas ļauj 'ietaupīt' transakciju numurus.
Ja transakcija tikai lasa datus, tā nemaina rindu versiju redzamību. Tādēļ sākumā apkalpojošais process piešķir transakcijām virtuālo numuru (virtual xid). Numurs sastāv no procesa identifikatora un secīga skaitļa.
Šī numura izsniegšana neprasa sinhronizāciju starp visām darbībām, un tādēļ tiek veikta ļoti ātri. Otrā iemesla, kādēļ izmantot virtuālos numurus, mēs iepazīsimies, kad runāsim par iesaldēšanu.
Virtuālie numuri netiek ņemti vērā datu attēlos.
Dažādos laikos sistēmā var būt virtuālas transakcijas ar numuriem, kas jau ir izmantoti, un tas ir pilnīgi normāli. Bet šādu numuru nav atļauts ierakstīt datu lapās, jo nākamajā piekļuvē lapai tam var zaudēt jebkādu jēgu.
=> SĀKUMS;
=> IZVĒLĒT txid_current_if_assigned();
txid_current_if_assigned
--------------------------
(1 rinda)
Ja transakcija sāk mainīt datus, tai tiek piešķirts īsts, unikāls transakcijas numurs.
=> ATJAUNOT accounts SET amount = amount - 1.00;
=> IZVĒLĒT txid_current_if_assigned();
txid_current_if_assigned
--------------------------
3667
(1 rinda)
=> COMMIT;
Iekļautās transakcijas
Saglabāšanas punkti
SQL ir definējuši saglabāšanas punktus (savepoint), kas ļauj atcelt daļu no transakcijas operācijām, neapstādinot to pilnībā. Tomēr tas neiekļaujas iepriekš minētajā schēmā, jo transakcijas statuss ir viens visām tās pārmaiņām, un fiziski nekādi dati netiek atjaunoti.
Lai īstenotu šādu funkcionalitāti, transakcija ar saglabāšanas punktu tiek sadalīta vairākās atsevišķās iekļautajās transakcijās (subtransaction), kuru statuss var tikt pārvaldīts atsevišķi.
Iekļautajām transakcijām ir savs numurs (lielāks nekā pamata transakcijas numurs). Iekļautās transakcijas statuss tiek ierakstīts parastajā veidā XACT, tomēr galīgais statuss ir atkarīgs no pamata transakcijas statusa: ja tā tiek atcelta, tiek atceltas arī visas iekļautās transakcijas.
Informācija par transakciju iekļautību tiek saglabāta failos katalogā PGDATA/pg_subtrans. Pieeja failiem notiek caur buferiem kopējā atmiņā, organizēti tāpat kā XACT buferi.
Nepieļaujiet sajaukt iekļautās transakcijas un patstāvīgās transakcijas. Patstāvīgās transakcijas nav savstarpēji atkarīgas, bet iekļautās — ir atkarīgas. Parastajā PostgreSQL nav patstāvīgu transakciju, un, iespējams, tas ir labāk: tās ir nepieciešamas ļoti, ļoti reti, un to klātbūtne citās DBMS veicina ļaunprātīgu izmantošanu, no kā vēlāk cieš visi.
Notīrīsim tabulu, uzsāksim transakciju un ievietosim rindu:
=> TRUNCATE TABLE t;
=> BEGIN;
=> INSERT INTO t(s) VALUES ('FOO');
=> SELECT txid_current();
txid_current
--------------
3669
(1 row)
=> SELECT xmin, xmax, * FROM t;
xmin | xmax | id | s
------+------+----+-----
3669 | 0 | 2 | FOO
(1 row)
=> SELECT * FROM heap_page('t',0);
ctid | state | xmin | xmax | t_ctid
-------+--------+------+-------+--------
(0,1) | normal | 3669 | 0 (a) | (0,1)
(1 row)
Tagad izveidosim saglabāšanas punktu un ievietosim vēl vienu rindu.
=> SAVEPOINT sp;
=> INSERT INTO t(s) VALUES ('XYZ');
=> SELECT txid_current();
txid_current
--------------
3669
(1 row)
Ņemiet vērā, ka funkcija txid_current() atgriež galvenās, nevis iekļautās darījuma numuru.
=> SELECT xmin, xmax, * FROM t;
xmin | xmax | id | s
------+------+----+-----
3669 | 0 | 2 | FOO
3670 | 0 | 3 | XYZ
(2 rows)
=> SELECT * FROM heap_page('t',0);
ctid | state | xmin | xmax | t_ctid
-------+--------+------+-------+--------
(0,1) | normal | 3669 | 0 (a) | (0,1)
(0,2) | normal | 3670 | 0 (a) | (0,2)
(2 rows)
Atgriezīsimies pie saglabāšanas punkta un ievietosim trešo rindu.
=> ROLLBACK TO sp;
=> INSERT INTO t(s) VALUES ('BAR');
=> SELECT xmin, xmax, * FROM t;
xmin | xmax | id | s
------+------+----+-----
3669 | 0 | 2 | FOO
3671 | 0 | 4 | BAR
(2 rows)
=> SELECT * FROM heap_page('t',0);
ctid | state | xmin | xmax | t_ctid
-------+--------+----------+-------+--------
(0,1) | normal | 3669 | 0 (a) | (0,1)
(0,2) | normal | 3670 (a) | 0 (a) | (0,2)
(0,3) | normal | 3671 | 0 (a) | (0,3)
(3 rows)
Lappus joprojām parāda rindu, kas pievienota atcelta iekļautā darījuma rezultātā.
Fiksēsim izmaiņas.
=> COMMIT;
=> SELECT xmin, xmax, * FROM t;
xmin | xmax | id | s
------+------+----+-----
3669 | 0 | 2 | FOO
3671 | 0 | 4 | BAR
(2 rows)
=> SELECT * FROM heap_page('t',0);
ctid | state | xmin | xmax | t_ctid
-------+--------+----------+-------+--------
(0,1) | normal | 3669 (c) | 0 (a) | (0,1)
(0,2) | normal | 3670 (a) | 0 (a) | (0,2)
(0,3) | normal | 3671 (c) | 0 (a) | (0,3)
(3 rows)
Tagad skaidri redzams, ka katram iekļautajam darījumam ir sava statusa.
Pamanīsim, ka iekļautos darījumus SQL nevar izmantot tieši, tas ir, nav iespējams uzsākt jaunu darījumu, nebeidzot esošo. Šis mehānisms tiek aktivizēts netieši, izmantojot saglabāšanas punktus, kā arī, apstrādājot PL/pgSQL izņēmumus un dažos citos eksotiskos gadījumos.
=> BEGIN;
SĀKT
=> BEGIN;
BRĪDINĀJUMS: darījums jau ir sākts
BEGIN
=> COMMIT;
COMMIT
=> COMMIT;
BRĪDINĀJUMS: nav aktīva darījuma
COMMIT
Kļūdas un operāciju atomaritāte
Kas notiks, ja operācijas izpildes laikā notiks kļūme? Piemēram, tā:
=> BEGIN;
=> SELECT * FROM t;
id | s
----+-----
2 | FOO
4 | BAR
(2 rows)
=> UPDATE t SET s = repeat('X', 1/(id-4));
KĻŪDA: dalīšana ar nulli
Notikusi kļūda. Tagad darījums tiek uzskatīts par pārtraukšanu, un tajā neviena operācija nav pieļaujama:
=> SELECT * FROM t;
KĻŪDA: pašreizējais darījums ir atcelts, komandas tiek ignorētas līdz darījuma bloka beigām
Un pat ja mēģinātu fiksēt izmaiņas, PostgreSQL paziņos par atcelšanu:
=> COMMIT;
ROLLBACK
Kāpēc pēc kļūmes nevar turpināt transakcijas izpildi? Jautājums ir tajā, ka kļūda varēja rasties tādā veidā, ka mēs būtu saņēmuši piekļuvi daļai izmaiņu — tiktu pārkāpta atomaritāte pat ne transakcijai, bet operatoram. Kā mūsu piemērā, kur operators līdz kļūmei paspēja atjaunot vienu rindu:
=> SELECT * FROM heap_page('t',0);
ctid | state | xmin | xmax | t_ctid
-------+--------+----------+-------+--------
(0,1) | normal | 3669 (c) | 3672 | (0,4)
(0,2) | normal | 3670 (a) | 0 (a) | (0,2)
(0,3) | normal | 3671 (c) | 0 (a) | (0,3)
(0,4) | normal | 3672 | 0 (a) | (0,4)
(4 rindiņas)
Jāteic, ka psql ir režīms, kas tomēr ļauj turpināt transakcijas darbu pēc kļūmes, it kā kļūdīgā operatora darbības tiktu atgrieztas.
=> set ON_ERROR_ROLLBACK on
=> BEGIN;
=> SELECT * FROM t;
id | s
----+-----
2 | FOO
4 | BAR
(2 rows)
=> UPDATE t SET s = repeat('X', 1/(id-4));
KĻŪDA: dalīšana ar nulli
=> SELECT * FROM t;
id | s
----+-----
2 | FOO
4 | BAR
(2 rows)
=> COMMIT;
Nav grūti nojaust, ka šādā režīmā psql faktiski novieto pirms katras komandas neuzmanīgu saglabāšanas punktu, un gadījumā, ja notiek kļūme, iniciē atgriešanos pie šī punkta. Šāds režīms pēc noklusējuma netiek lietots, jo saglabāšanas punktu iestatīšana (pat bez atgriešanās pie tiem) saistīta ar būtiskām sākotnējām izmaksām.
Avots: habr.com
