MVCC-3. String versiyaları

Beləliklə, bununla bağlı məsələləri nəzərdən keçirdik izolyasiya, və ətrafında geri çəkildi məlumatların aşağı səviyyədə təşkili. Və nəhayət, ən maraqlı hissəyə - simli versiyalara gəldik.

Başlıq

Artıq dediyimiz kimi, hər bir sıra verilənlər bazasında eyni vaxtda bir neçə versiyada mövcud ola bilər. Bir versiya digərindən müəyyən şəkildə fərqlənməlidir.Bu məqsədlə hər bir versiyada bu versiyanın fəaliyyət “vaxtını” təyin edən iki işarə vardır (xmin və xmax). Dırnaqlarda - çünki istifadə olunan zaman deyil, xüsusi artan sayğacdır. Və bu sayğac əməliyyat nömrəsidir.

(Həmişə olduğu kimi, reallıq daha mürəkkəbdir: sayğacın məhdud bit tutumu səbəbindən əməliyyat nömrəsi hər zaman arta bilməz. Lakin dondurulmağa çatdıqda bu detalları ətraflı nəzərdən keçirəcəyik.)

Sətir yaradıldıqda, INSERT əmrini verən əməliyyat nömrəsinə xmin təyin edilir və xmax boş qalır.

Sətir silindikdə, cari versiyanın xmax dəyəri SİLMƏ əməliyyatını həyata keçirən əməliyyatın nömrəsi ilə qeyd olunur.

Sətir UPDATE əmri ilə dəyişdirildikdə, əslində iki əməliyyat yerinə yetirilir: DELETE və INSERT. Sətirin cari versiyası YENİLƏNMƏni həyata keçirən əməliyyatın sayına bərabər xmax təyin edir. Sonra eyni sətirin yeni versiyası yaradılır; onun xmin dəyəri əvvəlki versiyanın xmax dəyəri ilə üst-üstə düşür.

Xmin və xmax sahələri sıra versiyasının başlığına daxil edilmişdir. Bu sahələrə əlavə olaraq, başlıq digərləri ehtiva edir, məsələn:

  • infomask bu versiyanın xassələrini təyin edən bitlər seriyasıdır. Onların kifayət qədər çoxu var; Əsas olanları tədricən nəzərdən keçirəcəyik.
  • ctid eyni xəttin növbəti, daha yeni versiyasına keçiddir. Xəttin ən yeni, ən cari versiyası üçün ctid bu versiyanın özünə aiddir. Nömrə (x,y) formasına malikdir, burada x səhifə nömrəsidir, y massivdəki indeks nömrəsidir.
  • null bitmap - Verilmiş versiyanın null dəyəri (NULL) ehtiva edən sütunlarını işarələyir. NULL normal məlumat tipi dəyərlərindən biri deyil, ona görə də atribut ayrıca saxlanmalıdır.

Nəticədə, başlıq kifayət qədər böyükdür - xəttin hər bir versiyası üçün ən azı 23 bayt və adətən NULL bitmapına görə daha çox. Cədvəl "dar"dırsa (yəni, bir neçə sütundan ibarətdir), əlavə xərclər faydalı məlumatdan daha çox ola bilər.

daxil

Yerləşdirmədən başlayaraq aşağı səviyyəli sətir əməliyyatlarının necə yerinə yetirildiyinə daha yaxından nəzər salaq.

Təcrübələr üçün iki sütun və onlardan birində indeks olan yeni cədvəl yaradaq:

=> CREATE TABLE t(
  id serial,
  s text
);
=> CREATE INDEX ON t(s);

Əməliyyata başladıqdan sonra bir sıra daxil edək.

=> BEGIN;
=> INSERT INTO t(s) VALUES ('FOO');

Budur cari əməliyyat nömrəmiz:

=> SELECT txid_current();
 txid_current 
--------------
         3664
(1 row)

Gəlin səhifənin məzmununa baxaq. pageinspect uzantısının heap_page_items funksiyası göstəricilər və sıra 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ə yığın sözü cədvəllərə aiddir. Bu terminin başqa bir qəribə istifadəsidir - bir yığın məlumdur məlumat strukturuCədvəllə heç bir əlaqəsi olmayan. Burada söz sıralı göstəricilərdən fərqli olaraq “hər şey bir yerə atılır” mənasında işlənir.

Funksiya məlumatları anlamaq çətin olan formatda “olduğu kimi” göstərir. Bunu anlamaq üçün məlumatın yalnız bir hissəsini tərk edəcəyik və deşifrə edəcəyik:

=> 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)

Nə etdik:

  • t_ctid ilə eyni görünməsi üçün indeks nömrəsinə sıfır əlavə edildi: (səhifə nömrəsi, indeks nömrəsi).
  • lp_flags göstəricisinin vəziyyəti deşifrə edildi. Burada "normaldır" - bu o deməkdir ki, göstərici əslində sətirin versiyasına istinad edir. Digər mənalara daha sonra baxacağıq.
  • Bütün məlumat bitlərindən indiyə qədər yalnız iki cüt müəyyən edilmişdir. xmin_committed və xmin_aborted bitləri xmin əməliyyat nömrəsinin yerinə yetirilib-keçirilmədiyini göstərir. İki oxşar bit xmax əməliyyat nömrəsinə aiddir.

Biz nə görürük? Sətir daxil etdiyiniz zaman cədvəl səhifəsində cərgənin ilk və yeganə versiyasını göstərən 1 nömrəli indeks görünəcək.

Simli versiyada xmin sahəsi cari əməliyyat nömrəsi ilə doldurulur. Əməliyyat hələ də aktivdir, ona görə də həm xmin_committed, həm də xmin_aborted bitləri təyin edilməyib.

Sətir versiyası ctid sahəsi eyni cərgəyə istinad edir. Bu o deməkdir ki, daha yeni versiya mövcud deyil.

Xmax sahəsi 0 dummy rəqəmi ilə doldurulur, çünki sətrin bu versiyası silinməyib və aktualdır. xmax_aborted biti təyin edildiyi üçün əməliyyatlar bu nömrəyə diqqət yetirməyəcək.

Tranzaksiya nömrələrinə məlumat bitləri əlavə etməklə oxunaqlılığı yaxşılaşdırmaq üçün daha bir addım ataq. Və bir funksiya yaradaq, çünki sorğuya bir dəfədən çox ehtiyacımız olacaq:

=> 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ıra versiyasının başlığında nə baş verdiyi daha aydın görünür:

=> SELECT * FROM heap_page('t',0);
 ctid  | state  | xmin | xmax  | t_ctid 
-------+--------+------+-------+--------
 (0,1) | normal | 3664 | 0 (a) | (0,1)
(1 row)

Bənzər, lakin əhəmiyyətli dərəcədə daha az təfərrüatlı məlumat xmin və xmax psevdo-sütunlarından istifadə edərək cədvəlin özündən əldə edilə bilər:

=> SELECT xmin, xmax, * FROM t;
 xmin | xmax | id |  s  
------+------+----+-----
 3664 |    0 |  1 | FOO
(1 row)

Fiksasiya

Əgər əməliyyat uğurla başa çatdırılıbsa, onun statusunu yadda saxlamalısınız - onun törədildiyini qeyd edin. Bunun üçün XACT adlı strukturdan istifadə olunur (və 10-cu versiyadan əvvəl o, CLOG (commit log) adlanırdı və bu ad hələ də müxtəlif yerlərdə tapıla bilər).

XACT sistem kataloqu cədvəli deyil; bunlar PGDATA/pg_xact qovluğundakı fayllardır. Onların hər bir əməliyyat üçün iki biti var: yerinə yetirildi və dayandırıldı - sıra versiyasının başlığında olduğu kimi. Bu məlumat yalnız rahatlıq üçün bir neçə fayla bölünmüşdür, dondurulması barədə düşünəndə bu məsələyə qayıdacağıq. Və bu fayllarla iş, digərləri kimi, səhifə-səhifə həyata keçirilir.

Beləliklə, XACT-da bir əməliyyat həyata keçirildikdə, bu əməliyyat üçün icra edilmiş bit təyin olunur. Və bu, törətmə zamanı baş verənlərin hamısıdır (baxmayaraq ki, biz hələ əvvəlcədən qeyd jurnalından danışmırıq).

Başqa bir əməliyyat indicə baxdığımız cədvəl səhifəsinə daxil olduqda, o, bir neçə suala cavab verməli olacaq.

  1. xmin əməliyyatı tamamlandı? Əgər belə deyilsə, o zaman sətirin yaradılmış versiyası görünməməlidir.
    Bu yoxlama nümunənin ümumi yaddaşında yerləşən və ProcArray adlanan başqa struktura baxmaqla həyata keçirilir. O, bütün aktiv proseslərin siyahısını ehtiva edir və hər biri üçün cari (aktiv) əməliyyatın nömrəsi göstərilir.
  2. Əgər tamamlanıbsa, onda necə - törətməklə və ya ləğv etməklə? Ləğv edilərsə, sıra versiyası da görünməməlidir.
    XACT məhz bunun üçündür. Lakin, XACT-ın son səhifələri RAM-da buferlərdə saxlansa da, hər dəfə XACT-ı yoxlamaq hələ də baha başa gəlir. Buna görə də, tranzaksiya statusu müəyyən edildikdən sonra string versiyasının xmin_committed və xmin_aborted bitlərinə yazılır. Bu bitlərdən biri təyin olunarsa, xmin əməliyyatının vəziyyəti məlum sayılır və növbəti tranzaksiya XACT-a daxil olmaq məcburiyyətində qalmayacaq.

Niyə bu bitlər əməliyyatın özü tərəfindən daxil edilmir? Daxiletmə baş verdikdə, əməliyyatın uğurlu olub-olmayacağı hələ bilinmir. Və öhdəçilik anında hansı səhifələrin hansı sətirlərdə dəyişdirildiyi artıq bəlli deyil. Belə səhifələr çox ola bilər və onları əzbərləmək faydasızdır. Bundan əlavə, bəzi səhifələr bufer keşindən diskə çıxarıla bilər; bitləri dəyişdirmək üçün onları yenidən oxumaq öhdəliyi əhəmiyyətli dərəcədə yavaşlatacaq.

Qənaətin mənfi tərəfi ondan ibarətdir ki, dəyişikliklərdən sonra istənilən əməliyyat (hətta sadə oxunuşu yerinə yetirən - SEÇİN) bufer keşində məlumat səhifələrini dəyişməyə başlaya bilər.

Beləliklə, dəyişikliyi düzəldək.

=> COMMIT;

Səhifədə heç nə dəyişməyib (lakin biz bilirik ki, əməliyyat statusu artıq XACT-da qeyd olunub):

=> 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 daxil olan əməliyyat xmin əməliyyat statusunu təyin etməli və onu məlumat 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)

Removal

Sətir silindikdə, cari silmə əməliyyatının nömrəsi cari versiyanın xmax sahəsinə yazılır və xmax_aborted biti təmizlənir.

Qeyd edək ki, aktiv tranzaksiyaya uyğun gələn xmax təyin edilmiş dəyəri sıra kilidi kimi çıxış edir. Başqa bir əməliyyat bu sıranı yeniləmək və ya silmək istəsə, xmax əməliyyatının tamamlanmasını gözləmək məcburiyyətində qalacaq. Bloklama haqqında daha sonra danışacağıq. Hələlik, sadəcə qeyd edirik ki, sıra kilidlərinin sayı qeyri-məhduddur. Onlar RAM-da yer tutmur və sistem performansı onların sayından əziyyət çəkmir. Düzdür, "uzun" əməliyyatların başqa mənfi cəhətləri də var, lakin bu barədə daha sonra.

Gəlin xətti silək.

=> BEGIN;
=> DELETE FROM t;
=> SELECT txid_current();
 txid_current 
--------------
         3665
(1 row)

Əməliyyat nömrəsinin xmax sahəsində yazıldığını, lakin məlumat bitlərinin təyin olunmadığını görürük:

=> SELECT * FROM heap_page('t',0);
 ctid  | state  |   xmin   | xmax | t_ctid 
-------+--------+----------+------+--------
 (0,1) | normal | 3664 (c) | 3665 | (0,1)
(1 row)

Ləğv etmək

Dəyişikliklərin dayandırılması, törədilməyə bənzər işləyir, yalnız XACT-da əməliyyat üçün ləğv edilmiş bit təyin olunur. Geri götürmək, törətmək qədər sürətlidir. Komanda ROLLBACK adlansa da, dəyişikliklər geri qaytarılmır: məlumat səhifələrində əməliyyatın dəyişdirə bildiyi hər şey dəyişməz olaraq qalır.

=> ROLLBACK;
=> SELECT * FROM heap_page('t',0);
 ctid  | state  |   xmin   | xmax | t_ctid 
-------+--------+----------+------+--------
 (0,1) | normal | 3664 (c) | 3665 | (0,1)
(1 row)

Səhifəyə daxil olduqda, status yoxlanılacaq və xmax_aborted işarə biti sıra versiyasına təyin ediləcək. Xmax nömrəsinin özü səhifədə qalır, amma heç kim ona baxmayacaq.

=> 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 row)

Update

Yeniləmə elə işləyir ki, sanki əvvəlcə sıranın cari versiyasını silib, sonra isə yenisini daxil edib.

=> BEGIN;
=> UPDATE t SET s = 'BAR';
=> SELECT txid_current();
 txid_current 
--------------
         3666
(1 row)

Sorğu bir sətir yaradır (yeni versiya):

=> SELECT * FROM t;
 id |  s  
----+-----
  1 | BAR
(1 row)

Ancaq səhifədə 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 rows)

Silinmiş versiya cari əməliyyat nömrəsi ilə xmax sahəsində qeyd olunur. Üstəlik, əvvəlki əməliyyat ləğv edildiyi üçün bu dəyər köhnənin üzərində yazılır. Və xmax_aborted biti təmizlənir, çünki cari əməliyyatın statusu hələ məlum deyil.

Xəttin ilk versiyası indi ikinciyə (t_ctid sahəsi) daha yenisi kimi istinad edir.

İndeks səhifəsində ikinci indeks görünür və ikinci sıra cədvəl səhifəsində ikinci versiyaya istinad edir.

Silinmə zamanı olduğu kimi, sətrin birinci versiyasında xmax dəyəri cərgənin kilidləndiyinin göstəricisidir.

Yaxşı, əməliyyatı tamamlayaq.

=> COMMIT;

Indeksləri

İndiyə qədər yalnız cədvəl səhifələrindən danışdıq. İndekslərin içərisində nə baş verir?

İndeks səhifələrindəki məlumatlar xüsusi indeks növündən asılı olaraq çox dəyişir. Və hətta bir indeks növü müxtəlif növ səhifələrə malikdir. Məsələn, B-ağacında metadata səhifəsi və "normal" səhifələr var.

Bununla belə, səhifədə adətən sətirlərə və cərgələrin özlərinə (eynilə cədvəl səhifəsi kimi) bir sıra göstəricilər var. Bundan əlavə, səhifənin sonunda xüsusi məlumatlar üçün yer var.

İndekslərdəki sıralar da indeksin növündən asılı olaraq çox fərqli strukturlara malik ola bilər. Məsələn, B-ağacı üçün yarpaq səhifələri ilə əlaqəli sətirlər indeksləşdirmə açarının dəyərini və müvafiq cədvəl sırasına istinadı (ctid) ehtiva edir. Ümumiyyətlə, indeks tamamilə fərqli bir şəkildə strukturlaşdırıla bilər.

Ən vacib məqam odur ki, heç bir növ indekslərdə sıra versiyaları yoxdur. Yaxşı, ya da hər bir xəttin tam olaraq bir versiya ilə təmsil olunduğunu güman edə bilərik. Başqa sözlə, indeks sətirinin başlığında xmin və xmax sahələri yoxdur. İndeksdən olan bağlantıların cərgələrin bütün cədvəl versiyalarına səbəb olduğunu güman edə bilərik - beləliklə, yalnız cədvələ baxaraq əməliyyatın hansı versiyanı görəcəyini anlaya bilərsiniz. (Həmişə olduğu kimi, bu, bütün həqiqət deyil. Bəzi hallarda, görünürlük xəritəsi prosesi optimallaşdıra bilər, lakin biz buna daha sonra daha ətraflı baxacağıq.)

Eyni zamanda, indeks səhifəsində hər iki versiyaya, həm cari, həm də köhnə versiyaya işarələr tapırıq:

=> SELECT itemoffset, ctid FROM bt_page_items('t_s_idx',1);
 itemoffset | ctid  
------------+-------
          1 | (0,2)
          2 | (0,1)
(2 rows)

Virtual əməliyyatlar

Təcrübədə PostgreSQL əməliyyat nömrələrini “saxlamağa” imkan verən optimallaşdırmalardan istifadə edir.

Əgər tranzaksiya yalnız məlumatları oxuyursa, bunun sıra versiyalarının görünməsinə heç bir təsiri yoxdur. Buna görə də, xidmət prosesi əvvəlcə əməliyyata virtual xid verir. Nömrə proses identifikatorundan və sıra nömrəsindən ibarətdir.

Bu nömrənin verilməsi bütün proseslər arasında sinxronizasiya tələb etmir və buna görə də çox sürətlidir. Virtual nömrələrdən istifadənin başqa bir səbəbi ilə donma haqqında danışarkən tanış olacağıq.

Məlumat anlıq görüntülərində virtual nömrələr heç bir şəkildə nəzərə alınmır.

Müxtəlif vaxtlarda sistemdə artıq istifadə edilmiş nömrələrlə virtual əməliyyatlar ola bilər və bu normaldır. Lakin belə bir nömrə məlumat səhifələrinə yazıla bilməz, çünki növbəti dəfə səhifəyə daxil olanda o, bütün mənasını itirə bilər.

=> BEGIN;
=> SELECT txid_current_if_assigned();
 txid_current_if_assigned 
--------------------------
                         
(1 row)

Əgər əməliyyat məlumatları dəyişməyə başlayırsa, ona real, unikal əməliyyat nömrəsi verilir.

=> UPDATE accounts SET amount = amount - 1.00;
=> SELECT txid_current_if_assigned();
 txid_current_if_assigned 
--------------------------
                     3667
(1 row)

=> COMMIT;

İç-içə əməliyyatlar

Xallara qənaət edin

SQL-də müəyyən edilmişdir xallara qənaət edin (savepoint), əməliyyatın bir hissəsini tamamilə kəsmədən ləğv etməyə imkan verir. Lakin bu, yuxarıdakı diaqrama uyğun gəlmir, çünki əməliyyat bütün dəyişikliklər üçün eyni statusa malikdir və fiziki olaraq heç bir məlumat geri qaytarılmır.

Bu funksiyanı həyata keçirmək üçün saxlama nöqtəsi ilə əməliyyat bir neçə ayrı hissəyə bölünür yuvalanmış əməliyyatlar statusu ayrıca idarə oluna bilən (alt əməliyyat).

Daxili əməliyyatların öz nömrəsi var (əsas əməliyyatın sayından çox). Yuvalanmış əməliyyatların statusu XACT-da adi qaydada qeyd olunur, lakin yekun status əsas tranzaksiya statusundan asılıdır: əgər o ləğv edilərsə, onda bütün daxili əməliyyatlar da ləğv edilir.

Əməliyyat yuvası haqqında məlumat PGDATA/pg_subtrans qovluğunda fayllarda saxlanılır. Fayllara XACT buferləri ilə eyni şəkildə təşkil edilmiş nümunənin ortaq yaddaşındakı buferlər vasitəsilə daxil olur.

Daxili əməliyyatları avtonom əməliyyatlarla qarışdırmayın. Avtonom əməliyyatlar heç bir şəkildə bir-birindən asılı deyil, iç içə əməliyyatlar. Adi PostgreSQL-də avtonom əməliyyatlar yoxdur və bəlkə də ən yaxşısı üçün: onlara çox, çox nadir hallarda ehtiyac duyulur və onların digər DBMS-lərdə olması sui-istifadəyə səbəb olur, bundan sonra hər kəs əziyyət çəkir.

Gəlin cədvəli təmizləyək, əməliyyata başlayaq və sıra daxil edək:

=> 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)

İndi bir saxlama nöqtəsi qoyaq və başqa bir xətt daxil edək.

=> SAVEPOINT sp;
=> INSERT INTO t(s) VALUES ('XYZ');
=> SELECT txid_current();
 txid_current 
--------------
         3669
(1 row)

Qeyd edək ki, txid_current() funksiyası daxili əməliyyat nömrəsini deyil, əsas əməliyyat nömrəsini qaytarır.

=> 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)

Saxlama nöqtəsinə qayıdıb üçüncü sətri daxil edək.

=> 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)

Səhifədə ləğv edilmiş daxili əməliyyatın əlavə etdiyi sıranı görməyə davam edirik.

Dəyişiklikləri düzəldirik.

=> 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)

İndi hər bir daxili əməliyyatın öz statusu olduğunu aydın görə bilərsiniz.

Nəzərə alın ki, daxili əməliyyatlar SQL-də açıq şəkildə istifadə edilə bilməz, yəni cari əməliyyatı tamamlamadan yeni əməliyyata başlaya bilməzsiniz. Bu mexanizm qənaət nöqtələrindən istifadə edərkən, həmçinin PL/pgSQL istisnalarına baxarkən və bir sıra başqa, daha ekzotik hallarda açıq şəkildə aktivləşdirilir.

=> BEGIN;
BEGIN
=> BEGIN;
WARNING:  there is already a transaction in progress
BEGIN
=> COMMIT;
COMMIT
=> COMMIT;
WARNING:  there is no transaction in progress
COMMIT

Səhvlər və əməliyyatların atomikliyi

Əməliyyatı yerinə yetirərkən xəta baş verərsə nə olar? Məsələn, bu kimi:

=> BEGIN;
=> SELECT * FROM t;
 id |  s  
----+-----
  2 | FOO
  4 | BAR
(2 rows)

=> UPDATE t SET s = repeat('X', 1/(id-4));
ERROR:  division by zero

Bir səhv baş verdi. İndi əməliyyat dayandırılmış sayılır və orada heç bir əməliyyata icazə verilmir:

=> SELECT * FROM t;
ERROR:  current transaction is aborted, commands ignored until end of transaction block

Dəyişiklikləri etməyə çalışsanız belə, PostgreSQL ləğv barədə məlumat verəcək:

=> COMMIT;
ROLLBACK

Niyə uğursuzluqdan sonra əməliyyat davam edə bilməz? Fakt budur ki, bir səhv elə bir şəkildə yarana bilər ki, biz dəyişikliklərin bir hissəsinə - hətta əməliyyatın atomikliyinə deyil, operator pozulacaq. Nümunəmizdə olduğu kimi, operator səhvdən əvvəl bir sətir yeniləməyi bacardı:

=> 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 rows)

Demək lazımdır ki, psql-də səhv operatorun hərəkətləri geri çəkilmiş kimi uğursuzluqdan sonra əməliyyatın davam etdirilməsinə hələ də imkan verən bir rejim var.

=> 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));
ERROR:  division by zero

=> SELECT * FROM t;
 id |  s  
----+-----
  2 | FOO
  4 | BAR
(2 rows)

=> COMMIT;

Təxmin etmək çətin deyil ki, bu rejimdə psql əslində hər bir əmrin qarşısında gizli saxlama nöqtəsi qoyur və uğursuzluq halında ona geri qayıtmağa başlayır. Bu rejim standart olaraq istifadə edilmir, çünki saxlama nöqtələrini təyin etmək (hətta onlara geri dönmədən) əhəmiyyətli yük tələb edir.

Davamı.

Mənbə: www.habr.com

Добавить комментарий