Beləliklə, biz mövzularını müzakirə etdik, danışdıq. Nəhayət, ən maraqlı hissəyə — sətir versiyalarına gəldik.
TLD
Artıq qeyd etdiyimiz kimi, hər bir sətir eyni anda verilənlər bazasında bir neçə versiyada ola bilər. Bir versiyanı digərindən ayırmaq üçün bir yol olmalıdır. Bu məqsədlə, hər bir versiyanın „vaxt“ göstəricisini müəyyən edən iki qeyd vardır (xmin və xmax). Əslində, burada vaxt deyil, xüsusi artırıcı sayğac istifadə olunur. Bu sayğac isə əməliyyat nömrəsidir.
(Adətən, bu daha mürəkkəbdir: əməliyyat nömrələri sayğacın məhdud bit dərinliyi səbəbindən hər zaman artıra bilmir. Lakin bu detallar üzərində, dondurma məqamına gəldikdə daha ətraflı danışacağıq.)
Sətir yaradıldıqda, xmin dəyəri INSERT əmri icra edən əməliyyatın nömrəsinə təyin olunur, xmax isə boş qalır.
Sətir silindikdə, cari versiyanın xmax dəyəri DELETE əmri icra edən əməliyyatın nömrəsi ilə işarələnir.
Sətir UPDATE əmri ilə dəyişdiriləndə, əslində iki əməliyyat həyata keçirilir: DELETE və INSERT. Cari versiyanın xmax dəyəri UPDATE əmri icra edən əməliyyatın nömrəsi ilə təyin olunur. Daha sonra eyni sətirin yeni bir versiyası yaradılır; xmin dəyəri əvvəlki versiyanın xmax dəyəri ilə eynidir.
xmin və xmax sahələri sətir versiyasının başlığında yerləşir. Bu sahələrdən əlavə, başlıqda başqa sahələr də var, məsələn:
- infomask — bu versiyanın xüsusiyyətlərini müəyyən edən bitlərdən ibarət sıra. Bu bitlərin kifayət qədər çoxudur; biz onların əsaslarını tədricən müzakirə edəcəyik.
- ctid — eyni sətirin daha yeni versiyasına işarədir. Ən yeni, aktual, versiyanın ctid-si özü bu versiyaya işarə edir. Nömrə (x,y) formasındadır, burada x səhifənin nömrəsidir, y isə massivdəki göstəricinin ardıcıl nömrəsidir.
- müəyyən olmayan dəyərlərin bit xəritəsi — bu versiyadakı müəyyən olmayan dəyərləri (NULL) özündə əks etdirir. NULL adi tip dəyərlərdən biri deyil, buna görə bu əlaməti ayrı saxlamaq lazım gəlir.
Nəticədə, başlıq olduqca böyük olur — hər sətir versiyası üçün minimum 23 bayt, adətən isə NULL bit xəritəsi səbəbindən daha çox. Həmçinin, əgər cədvəl 'dar'dırsa (yəni az sütun ehtiva edirsə), xərclər faydalı məlumatdan daha çox ola bilər.
Daxil etmə
Gəlin aşağı səviyyədə string əməliyyatlarının necə həyata keçirildiyini daha ətraflı nəzərdən keçirək və əlavə etmə ilə başlayırıq.
Təcrübələr üçün iki sütunu və onlardan birinə indeks olan yeni bir cədvəl yaradacağıq:
=> CREATE TABLE t(
id serial,
s text
);
=> CREATE INDEX ON t(s);
Bir sətir əlavə edək, əvvəlcə tranzaksiyanı başlayaraq.
=> BEGIN;
=> INSERT INTO t(s) VALUES ('FOO');
Cari tranzaksiyamızın nömrəsini görək:
=> SELECT txid_current();
txid_current
--------------
3664
(1 row)
Səhifənin məzmununa nəzər salaq. pageinspect genişlənməsinin heap_page_items funksiyası göstəricilər və sətir versiyaları haqqında məlumat əldə etməyə imkan verir:
=> SELECT * FROM heap_page_items(get_raw_page('t',0)) gx
-[ RECORD 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
Qeyd edək ki, PostgreSQL-də heap (kütlə) termini cədvəlləri ifadə edir. Bu, terminlərin daha bir qəribə istifadəsidir — heap tanınmış , cədvəl adı ilə heç bir əlaqəsi yoxdur. Burada bu söz "hamısı yığılıb" mənasında istifadə olunur, sıraya salınmış indekslərdən fərqli olaraq.
Funksiya məlumatları "oldugun kimi" göstərəcək, ancaq bu formatı anlamaq çətindir. Bir az sadələşdirmək üçün yalnız bir hissəni saxlayacağıq və onun mənasını açacağıq:
=> SELECT '(0,'||lp||')' AS ctid,
CASE lp_flags
WHEN 0 THEN 'unused'
WHEN 1 THEN 'normal'
WHEN 2 THEN 'redirect to '||lp_off
WHEN 3 THEN 'dead'
END AS state,
t_xmin as xmin,
t_xmax as xmax,
(t_infomask & 256) > 0 AS xmin_commited,
(t_infomask & 512) > 0 AS xmin_aborted,
(t_infomask & 1024) > 0 AS xmax_commited,
(t_infomask & 2048) > 0 AS xmax_aborted,
t_ctid
FROM heap_page_items(get_raw_page('t',0)) gx
-[ RECORD 1 ]-+-------
ctid | (0,1)
state | normal
xmin | 3664
xmax | 0
xmin_commited | f
xmin_aborted | f
xmax_commited | f
xmax_aborted | t
t_ctid | (0,1)
Budur etdiyimiz:
- Göstəricinin nömrəsinə sıfır əlavə etdik ki, onu t_ctid ilə eyni formata gətirək: (səhifə nömrəsi, göstərici nömrəsi).
- Göstəricinin lp_flags vəziyyətini açıqladıq. Burada "normal"dır — bu, göstəricinin həqiqətən sətirin versiyasına istinad etdiyini bildirir. Digər dəyərləri sonra müzakirə edəcəyik.
- Bütün məlumat bitlərindən yalnız iki cütü seçdik. Bitlər xmin_committed və xmin_aborted, xmin nömrəsinə sahib tranzaksiyanın təsdiq edilib-edilmədiyini (lazım gələrsə ləğv edildiyini) göstərir. İki oxşar bit xmax nömrəsinə aid tranzaksiya üçün də mövcuddur.
Nə görürük? Cədvəl səhifəsinə sətir əlavə edildikdə, bir göstərici görünəcək, bu da birinci və tək sətirin versiyasına istinad edir.
Sətirin versiyasında xmin sahəsi cari tranzaksiyanın nömrəsi ilə doldurulur. Tranzaksiya hələ də aktivdir, buna görə həm xmin_committed, həm də xmin_aborted bitləri quraşdırılmayıb.
Sətirin versiyasındakı ctid sahəsi eyni sətirə istinad edir. Bu, daha yeni bir versiyanın olmadığını göstərir.
Xmax sahəsi saxta 0 nömrəsi ilə doldurulub, çünki bu sətirin versiyası silinməyib və aktualdır. Tranzaksiyalar bu nömrəyə diqqət etməyəcək, çünki xmax_aborted bitləri quraşdırılıb.
Oxunabilirliyi artırmaq üçün tranzaksiya nömrələrinə məlumat bitləri əlavə edək. Və bu sorğunu bir daha istifadə edəcəyimiz üçün funksiya yaradacağıq:
=> 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 'unused'
WHEN 1 THEN 'normal'
WHEN 2 THEN 'redirect to '||lp_off
WHEN 3 THEN 'dead'
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;
Bu formada, sətirin versiyası başlığında nələrin baş verdiyini xeyli aydın görmək olar:
=> SELECT * FROM heap_page('t',0);
ctid | state | xmin | xmax | t_ctid
-------+--------+------+-------+--------
(0,1) | normal | 3664 | 0 (a) | (0,1)
(1 row)
Eyni məlumatı, lakin zəruri dərəcədə daha az ətraflı olaraq, cədvəldən əldə etmək mümkündür; bunu istifadə edərək psixodal sütunlar xmin və xmax:
=> SELECT xmin, xmax, * FROM t;
xmin | xmax | id | s
------+------+----+-----
3664 | 0 | 1 | FOO
(1 row)
Təsdiq
Tranzaksiya uğurla başa çatdıqda onun vəziyyətini yadda saxlamaq lazımdır – onu təsdiq olunmuş kimi qeyd etmək. Bunun üçün XACT adlı bir struktur istifadə olunur (10-cı versiyadan əvvəl buna CLOG (commit log) deyilir və bu ad hələ də müxtəlif yerlərdə rast gəlinə bilər).
XACT - sistem kataloqunun cədvəli deyil; bunlar PGDATA/pg_xact kataloqundakı fayllardır. Hər tranzaksiya üçün iki bit ayrılıb: committed və aborted - dəqiq olaraq sətirin versiyası başlığındakı kimi. Bu məlumat bir neçə fayla bölünmüşdür, yalnız rahatlıq məqsədilə. Biz bu məsələyə yenidən qayıdacağıq, dondurma zamanı baxarkən. Bu faylarla işləmə prosesi bütün digər fayllar kimi səhifə-səhifə aparılır.
Beləliklə, XACT-də əməliyyat qeydiyyata alındıqda, müvafiq əməliyyat üçün committed bit-i təyin olunur. Bu, qeydiyyat zamanı baş verən hər şeydir (doğrudur, hələlik ön yazma jurnalından danışmırıq).
Başqa bir əməliyyat, indi baxdığımız cədvəl səhifəsinə müraciət etdikdə, bir neçə suala cavab verməlidir.
- Xmin əməliyyatı tamamlanmışdırmı? Əgər yoxdursa, yaradılan sətir versiyası görünməməlidir.
Belə bir yoxlama, instansiyanın ortaq yaddaşında yerləşən və ProcArray adlanan başqa bir struktura baxaraq həyata keçirilir. Burada bütün aktiv proseslərin siyahısı var və hər biri üçün cari (aktiv) əməliyyatın nömrəsi qeyd olunur. - Əgər tamamlanmışdırsa, necə — qeydiyyatla, yoxsa ləğv edilərək? Əgər ləğv edilərəkdirsə, sətir versiyası da görünməməlidir.
Bunun üçün XACT məhz buna lazımdır. Lakin, son XACT səhifələri operativ yaddaşda tamponlarda saxlanılsa da, hər dəfə XACT-ı yoxlamaq əlavə yükdür. Buna görə, bir dəfə müəyyən edilmiş əməliyyatın statusu sətir versiyasının xmin_committed və xmin_aborted bitlərinə yazılır. Əgər bu bitlərdən biri aktivdirsə, xmin əməliyyatının vəziyyəti məlum hesab olunur və növbəti əməliyyata artıq XACT-a müraciət etmək lazım olmayacaq.
Bu bitlərin insert ulduzunu yerinə yetirən əməliyyat tərəfindən təyin edilməməsinin səbəbi nədir? Insert həyata keçirilərkən, əməliyyat hələ də uğurla başa çatacağını bilmir. Həmçinin, qeydiyyat zamanı hansı sətirlərin hansı səhifələrdə dəyişdirildiyi artıq aydın deyil. Belə səhifələrin sayı çox ola bilər və onları yadda saxlamaq sərfəli deyil. Üstəlik, bəzi səhifələr tamponlardakı diskdən təzyiqə düşə bilər; onların yenidən oxunması, bitləri dəyişmək üçün qeydiyyatı əhəmiyyətli dərəcədə yavaşlatmış olardı.
Saxlamaqdan irəliləyən tərəf, hər hansı bir dəyişiklikdən sonra, hər hansı bir əməliyyat (hətta sadə oxuma, SELECT yerinə yetirən) məlumat səhifələrini tampon yedəyində dəyişməyə başlaya bilər.
Beləliklə, dəyişiklikləri qeyd edək.
=> COMMIT;
Səhifədə heç bir dəyişiklik baş verməmişdir (amma biz bilirik ki, əməliyyat statusu artıq XACT-a qeyd olunmuşdur):
=> SELECT * FROM heap_page('t',0);
ctid | state | xmin | xmax | t_ctid
-------+--------+------+-------+--------
(0,1) | normal | 3664 | 0 (a) | (0,1)
(1 row)
İndi səhifəyə ilk müraciət edən əməliyyat, xmin əməliyyatının statusunu müəyyən etməli və onu informasiya bitlərinə yazmalıdır:
=> 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)
Sil
Xmax sahəsində bir sətir silindikdə, silinən müvafiq transaksiyanın nömrəsi yazılır və xmax_aborted bitinin dəyəri sıfırlanır.
Qeyd edək ki, aktiv transaksiyaya uyğun olan xmax dəyəri sətirin bloklanması kimi çıxış edir. Əgər başqa bir tranzaksiya bu sətiri yeniləmək və ya silmək istəyirsə, o, xmax tranzaksiyasının başa çatmasını gözləməyə məcbur olacaq. Bloklamalar haqqında daha sonra danışacağıq. Hal-hazırda yalnız qeyd edək ki, sətir bloklamalarının sayı heç bir məhdudiyyətə malik deyil. Onlar operativ yaddaşda yer tutmur və sistemin performansı onların sayından təsirlənmir. Lakin
Sətiri silək.
=> BEGIN;
=> DELETE FROM t;
=> SELECT txid_current();
txid_current
--------------
3665
(1 sətir)
Görürük ki, tranzaksiya nömrəsi xmax sahəsində yazılıb, amma informasiya bitləri quraşdırılmayıb:
=> SELECT * FROM heap_page('t',0);
ctid | state | xmin | xmax | t_ctid
-------+--------+----------+------+--------
(0,1) | normal | 3664 (c) | 3665 | (0,1)
(1 sətir)
Ləğv et
Dəyişikliklərin ləğvi, tamamlama ilə eyni şəkildə işləyir, yalnız XACT-da tranzaksiya üçün aborted biti təyin olunur. Ləğv etmə, tamamlama qədər sürətlə həyata keçirilir. Bəlkə də həmin komandaya ROLLBACK deyilir, amma dəyişikliklərin ləğvi baş vermir: tranzaksiyanın məlumat səhifələrində dəyişdirdiyi hər şey dəyişməz qalır.
=> ROLLBACK;
=> SELECT * FROM heap_page('t',0);
ctid | state | xmin | xmax | t_ctid
-------+--------+----------+------+--------
(0,1) | normal | 3664 (c) | 3665 | (0,1)
(1 sətir)
Səhifəyə müraciət edildikdə vəziyyət yoxlanılacaq və sətirin versiyasına xmax_aborted bitinin dəyəri qoyulacaq. Xmax nömrəsi səhifədə qalacaq, amma artıq bununla heç kim maraqlanmayacaq.
=> 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) | 3665 (a) | (0,1)
(1 sətir)
Yeniləmə
Yeniləmə, əvvəlcə cari sətirin versiyasının silinməsi, sonra isə yeni versiyanın əlavə olunması kimi işləyir.
=> BEGIN;
=> UPDATE t SET s = 'BAR';
=> SELECT txid_current();
txid_current
--------------
3666
(1 sətir)
Sorğu bir sətir (yeni versiya) təqdim edir:
=> SELECT * FROM t;
id | s
----+-----
1 | BAR
(1 sətir)
Amma səhifədə biz hər iki versiyanı görürük:
=> SELECT * FROM heap_page('t',0);
ctid | state | xmin | xmax | t_ctid
-------+--------+----------+-------+--------
(0,1) | normal | 3664 (c) | 3666 | (0,2)
(0,2) | normal | 3666 | 0 (a) | (0,2)
(2 sətir)
Silinmiş versiya cari tranzaksiyanın nömrəsi ilə xmax sahəsində işarələnib. Həmçinin bu dəyər köhnə üzərinə yazılıb, çünki əvvəlki tranzaksiya ləğv edilib. Və xmax_aborted biti sıfırlanıb, çünki cari tranzaksiyanın vəziyyəti hələ məlum deyil.
Sətirin birinci versiyası ikinciyə (t_ctid sahəsi) daha yeni kimi istinad edir.
İndeks səhifəsində ikinci nişan və ikinci versiyaya müraciət edən ikinci sətir görünür.
Silinmə zamanı olduğu kimi, sətirin ilk versiyasında olan xmax dəyəri, sətirin kilidləndiyini göstərir.
İndi isə əməliyyatı başa vururuq.
=> COMMIT;
İndeksler
Hələlik yalnız cədvəl səhifələrindən danışırsaq. Bəs indekslərin içində nə baş verir?
İndeks səhifələrindəki məlumat konkret indeks tipindən güclü asılıdır. Hətta bir indeks tipinin fərqli səhifə növləri ola bilər. Məsələn, B-ağacında metadatalar səhifəsi və "adi" səhifələr var.
Əksinə, səhifədə sətirlərə və onların özlərinə işarələr olan bir sıra vardır (cədvəl səhifəsində olduğu kimi). Bundan əlavə, səhifənin sonunda xüsusi məlumatlar üçün yer ayrılır.
İndeks sətirləri də indeks tipindən asılı olaraq çox fərqli struktura malik ola bilər. Məsələn, B-ağacında yarpaq səhifələrinə aid sətirlər indeks açarının dəyərini və cədvəl sətirinin müvafiq referansı (ctid) saxlayır. Ümumiyyətlə, indeks tamamilə fərqli bir şəkildə təşkil oluna bilər.
Ən vacib məqam, hər cür indekstdə sətir versiyalarının olmamasıdır. Yəni, hər bir sətir yalnız bir versiya ilə təqdim olunur. Başqa sözlə, indeks sətirinin başlığında xmin və xmax sahələri yoxdur. İndeksdən gələn linklərin bütün cədvəl sətir versiyalarına yönəldiyini düşünmək olar - beləliklə, əməliyyatın hansı versiyanı görəcəyini yalnız cədvələ baxaraq başa düşmək mümkündür. (Adətən, bu bütün həqiqət deyil. Bəzi hallarda görünmə xəritəsi prosesi optimallaşdırmağa imkan verir, amma bunu daha sonra müzakirə edəcəyik.)
Bununla belə, indeks səhifəsində həm aktual, həm də köhnə versiyalara işarələr aşkar edirik:
=> SELECT itemoffset, ctid FROM bt_page_items('t_s_idx',1);
itemoffset | ctid
------------+-------
1 | (0,2)
2 | (0,1)
(2 sətir)
Xəyalı əməliyyatlar
Təcrübədə PostgreSQL optimallaşdırma tətbiq edir ki, bu da "işlətdikdən" sonra əhəmiyyətli sayları saxlayır.
Əgər əməliyyat yalnız məlumatları oxuyursa, o, sətir versiyalarının görünməsinə heç bir təsir etmir. Buna görə, əvvəlcə xidmət prosesi əməliyyata virtual bir nömrə (virtual xid) verir. Bu nömrə proses identifikatoru və ardıcıllıq nömrəsindən ibarətdir.
Bu nömrənin verilməsi bütün proseslər arasında sinxronizasiyanı tələb etmir və bu səbəbdən çox sürətlə icra olunur. Virtual nömrələrin digər istifadə səbəbi ilə tanış olacağıq, bu da donma haqqında danışdığımız zaman baş verəcək.
Virtual nömrələr məlumat qeydlərində heç bir şəkildə nəzərə alınmır.
Fərqli zamanlarda sistemdə artıq istifadə olunmuş nömrələri olan virtual tranzaksiyalar ola bilər və bu normaldır. Lakin belə bir nömrə məlumat səhifələrinə yazılmamalıdır, çünki səhifəyə növbəti müraciət zamanı onun hər hansı bir mənası itə bilər.
=> BEGIN;
=> SELECT txid_current_if_assigned();
txid_current_if_assigned
--------------------------
(1 sətir)
Əgər tranzaksiya məlumatları dəyişməyə başlayarsa, ona həqiqi, unikal tranzaksiya nömrəsi verilir.
=> UPDATE accounts SET amount = amount - 1.00;
=> SELECT txid_current_if_assigned();
txid_current_if_assigned
--------------------------
3667
(1 sətir)
=> COMMIT;
Nəzərə alınmış tranzaksiyalar
Saxlama nöqtələri
SQL-də müəyyən edilir saxlama nöqtələri (savepoint), tranzaksiyanın bir hissəsini ləğv etməyə imkan tanıyır, tamamilə dayandırmadan. Lakin bu, yuxarıda göstərilən sxemə uyğun gəlmir, çünki tranzaksiyanın statusu bütün dəyişiklikləri üçün birdir və fiziki olaraq heç bir məlumat geri qaytarılmır.
Belə bir funksionallığı həyata keçirmək üçün saxlama nöqtəsi olan tranzaksiya bir neçə ayrı nəzərə alınmış tranzaksiyaya (subtransaction), statusu ilə ayrıca idarə edilə bilər.
Nəzərə alınmış tranzaksiyaların öz nömrəsi var (əsas tranzaksiyadan daha böyük). Nəzərə alınmış tranzaksiyaların statusu adətən XACT-da qeyd olunur, lakin son status əsas tranzaksiyanın statusundan asılıdır: əgər o, ləğv edilibsə, onda bütün nəzərə alınmış tranzaksiyalar da ləğv ediləcək.
Nəzərə alınmış tranzaksiyalarla bağlı məlumatlar PGDATA/pg_subtrans kataloqundakı fayllarda saxlanılır. Fayllara müraciət ümumi yaddaş nümunəsinin tamponları vasitəsilə həyata keçirilir, bu tamponlar da XACT tamponları kimi təşkil olunur.
Nəzərə alınmış tranzaksiyaları öz-özlüyündə tranzaksiyalarla qarışdırmayın. Öz-özlüyündə tranzaksiyalar bir-birinə asılı deyil, nəzərə alınmışlar isə asılıdır. Adi PostgreSQL-də öz-özlüyündə tranzaksiyalar yoxdur və bəlkə də yaxşıdır: onlar çox nadir hallarda lazım olur, digər DBMS-lərdə mövcudluğu isə sui-istifadəyə yol açır və bu, sonra hamının ziyanına olur.
Cədvəli təmizləyək, tranzaksiyanı başlayaq və bir sətir daxil edək:
=> TƏSVİRİ CƏDVƏL t;
=> BAŞLA;
=> t(s) DAXİL ET VALUES ('FOO');
=> SELECT txid_current();
txid_current
--------------
3669
(1 sətir)
=> SELECT xmin, xmax, * FROM t;
xmin | xmax | id | s
------+------+----+-----
3669 | 0 | 2 | FOO
(1 sətir)
=> SELECT * FROM heap_page('t',0);
ctid | vəziyyət | xmin | xmax | t_ctid
-------+--------+------+-------+--------
(0,1) | normal | 3669 | 0 (a) | (0,1)
(1 sətir)
İndi bir qorunma nöqtəsi qoyacağıq və başqa bir sətir əlavə edəcəyik.
=> SAVEPOINT sp;
=> t(s) DAXİL ET VALUES ('XYZ');
=> SELECT txid_current();
txid_current
--------------
3669
(1 sətir)
Qeyd edin ki, txid_current() funksiyası əsas, yoxsa daxili transaksiyanı göstərir.
=> SELECT xmin, xmax, * FROM t;
xmin | xmax | id | s
------+------+----+-----
3669 | 0 | 2 | FOO
3670 | 0 | 3 | XYZ
(2 sətir)
=> SELECT * FROM heap_page('t',0);
ctid | vəziyyət | xmin | xmax | t_ctid
-------+--------+------+-------+--------
(0,1) | normal | 3669 | 0 (a) | (0,1)
(0,2) | normal | 3670 | 0 (a) | (0,2)
(2 sətir)
Qorunma nöqtəsinə qayıdacağıq və üçüncü bir sətir əlavə edəcəyik.
=> ROLLBACK TO sp;
=> t(s) DAXİL ET VALUES ('BAR');
=> SELECT xmin, xmax, * FROM t;
xmin | xmax | id | s
------+------+----+-----
3669 | 0 | 2 | FOO
3671 | 0 | 4 | BAR
(2 sətir)
=> SELECT * FROM heap_page('t',0);
ctid | vəziyyət | 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 sətir)
Səhifədə ləğv olunmuş daxili transaksiyadan əlavə olunan sətiri görməyə davam edirik.
Dəyişiklikləri mühafizə edirik.
=> COMMIT;
=> SELECT xmin, xmax, * FROM t;
xmin | xmax | id | s
------+------+----+-----
3669 | 0 | 2 | FOO
3671 | 0 | 4 | BAR
(2 sətir)
=> SELECT * FROM heap_page('t',0);
ctid | vəziyyət | 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 sətir)
İndi gözəl görünsə də, hər daxili transaksiyanın öz vəziyyəti var.
Qeyd edək ki, daxili transaksiyaları SQL-də açıq şəkildə istifadə etmək olmaz, yəni cari transaksiyanı tamamlamadan yeni birini başlatmaq olmaz. Bu mexanizm qorunma nöqtələrindən istifadə edərkən və PL/pgSQL istisnalarının işlənməsi zamanı, habelə bir neçə daha ekzotik hallarda qeyri-şəffaf şəkildə tətbiq olunur.
=> BAŞLA;
BAŞLA
=> BAŞLA;
XƏBƏRDARLIQ: artıq bir tranzaksiya başlatmışdır
BAŞLA
=> COMMIT;
COMMIT
=> COMMIT;
XƏBƏRDARLIQ: aktiv tranzaksiya yoxdur
COMMIT
Xətalar və əməliyyatların atomikliyi
Əməliyyat zamanı bir səhv baş verərsə, nə olacaq? Məsələn, belə:
=> BAŞLA;
=> t-dən seçin;
id | s
----+-----
2 | FOO
4 | BAR
(2 sətir)
=> UPDATE t SET s = repeat('X', 1/(id-4));
XƏTA: sıfıra bölünmə
Səhv baş verdi. İndi transaksiya kəsildi və onun içərisində heç bir əməliyyat icazə verilmir:
=> SELECT * FROM t;
XƏTA: cari tranzaksiya ləğv edilib, əmr blokun sonunda gözlənilir
Və dəyişiklikləri sistemə salmağa çalışsaq belə, PostgreSQL ləğv olunduğunu bildirecək:
=> COMMIT;
ROLLBACK
Niyə uğursuzluqdan sonra əməliyyatı davam etdirmək mümkün deyil? Məsələ ondadır ki, xətanın baş verməsi, dəyişikliklərin bir hissəsinə erişim əldə etmə imkanı yarada bilərdi — atomarlığın pozulması yalnız əməliyyatın deyil, eyni zamanda operatorun da baş vermiş olardı. Məsələn, bizim misalımızda operator xətadan əvvəl bir sətiri güncəlləməyə müvəffəq oldu:
=> 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 sətir)
Qeyd etmək lazımdır ki, psql-də, həqiqətən də, uğursuzluqdan sonra əməliyyatı davam etdirməyə imkan verən bir rejim var ki, burada xətalı operatorun əməliyyatları geri qaytarılır.
=⇒ set ON_ERROR_ROLLBACK on
=⇒ BEGIN;
=⇒ SELECT * FROM t;
id | s
----+-----
2 | FOO
4 | BAR
(2 sətir)
=> UPDATE t SET s = repeat('X', 1/(id-4));
XƏTA: sıfıra bölünmə
=> SELECT * FROM t;
id | s
----+-----
2 | FOO
4 | BAR
(2 sətir)
=> COMMIT;
Başqa sözlə, belə bir rejimdə psql hər bir əmrdən əvvəl qoyulmamış bir xilasetmə nöqtəsi yaradır və uğursuzluq halında ona geri dönməyi təşviq edir. Bu rejim standart olaraq istifadə edilmir, çünki xilasetmə nöqtələrinin qurulması (hətta onlara geri döndüyü halda belə) əhəmiyyətli xərclərlə əlaqələndirilir.
Mənbə: habr.com
