MVCC-3. String versiyalari

Shunday qilib, biz bilan bog'liq masalalarni ko'rib chiqdik izolyatsiya, va atrofida chekinish qildi ma'lumotlarni past darajada tashkil qilish. Va nihoyat, biz eng qiziqarli qismga keldik - string versiyalari.

header

Yuqorida aytib o'tganimizdek, har bir qator bir vaqtning o'zida ma'lumotlar bazasida bir nechta versiyalarda mavjud bo'lishi mumkin. Bitta versiya boshqasidan qandaydir farqlanishi kerak.Shuning uchun har bir versiyada ushbu versiyaning amal qilish β€œvaqtini” belgilovchi ikkita belgi mavjud (xmin va xmax). Qo'shtirnoq ichida - chunki u vaqt kabi emas, balki maxsus o'sish hisoblagichi ishlatiladi. Va bu hisoblagich tranzaksiya raqamidir.

(Odatdagidek, haqiqat yanada murakkab: hisoblagichning cheklangan bit sig'imi tufayli tranzaksiya raqami har doim ham ko'payib bo'lmaydi. Lekin biz muzlatishga kelganimizda bu tafsilotlarni batafsil ko'rib chiqamiz.)

Qator yaratilganda, xmin INSERT buyrug'ini bergan tranzaksiya raqamiga o'rnatiladi va xmax bo'sh qoladi.

Qator o'chirilganda, joriy versiyaning xmax qiymati DELETE amalga oshirilgan tranzaksiya raqami bilan belgilanadi.

Qator UPDATE buyrug'i bilan o'zgartirilsa, aslida ikkita amal bajariladi: DELETE va INSERT. Qatorning joriy versiyasi UPDATE amalga oshirilgan tranzaksiya soniga teng xmaxni o'rnatadi. Keyin bir xil satrning yangi versiyasi yaratiladi; uning xmin qiymati oldingi versiyaning xmax qiymatiga to'g'ri keladi.

Xmin va xmax maydonlari satr versiyasi sarlavhasiga kiritilgan. Ushbu maydonlarga qo'shimcha ravishda, sarlavhada boshqalar mavjud, masalan:

  • infomask bu versiyaning xususiyatlarini belgilaydigan bitlar seriyasidir. Ularning ko'pi bor; Biz asta-sekin asosiylarini ko'rib chiqamiz.
  • ctid - xuddi shu qatorning keyingi, yangi versiyasiga havola. Chiziqning eng yangi, eng joriy versiyasi uchun ctid ushbu versiyaning o'ziga ishora qiladi. Raqam (x,y) ko'rinishga ega, bu erda x - sahifa raqami, y - massivdagi indeks raqami.
  • null bitmap - berilgan versiyaning null qiymatini (NULL) o'z ichiga olgan ustunlarini belgilaydi. NULL normal ma'lumotlar turi qiymatlaridan biri emas, shuning uchun atribut alohida saqlanishi kerak.

Natijada, sarlavha juda katta - satrning har bir versiyasi uchun kamida 23 bayt va odatda NULL bitmap tufayli ko'proq. Agar jadval "tor" bo'lsa (ya'ni, bir nechta ustunlar mavjud bo'lsa), qo'shimcha xarajatlar foydali ma'lumotlardan ko'proq narsani olishi mumkin.

joylashtiring

Keling, kiritishdan boshlab, past darajadagi satr operatsiyalari qanday bajarilishini batafsil ko'rib chiqaylik.

Tajribalar uchun ikkita ustun va ulardan birida indeksli yangi jadval tuzamiz:

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

Tranzaktsiyani boshlagandan so'ng bitta qatorni kiritamiz.

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

Mana bizning joriy tranzaksiya raqamimiz:

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

Keling, sahifaning mazmunini ko'rib chiqaylik. Pageinspect kengaytmasining heap_page_items funksiyasi ko'rsatkichlar va qator versiyalari haqida ma'lumot olish imkonini beradi:

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

E'tibor bering, PostgreSQL-dagi to'p so'zi jadvallarni anglatadi. Bu atamaning yana bir g'alati qo'llanilishi - bir uyum ma'lum ma'lumotlar tuzilishi, bu jadval bilan hech qanday umumiylik yo'q. Bu erda so'z tartibli ko'rsatkichlardan farqli o'laroq, "hamma narsa birga tashlanadi" ma'nosida qo'llaniladi.

Funktsiya ma'lumotlarni tushunish qiyin bo'lgan formatda "xuddi shunday" ko'rsatadi. Buni aniqlash uchun biz ma'lumotlarning faqat bir qismini qoldiramiz va uni shifrlaymiz:

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

Mana biz nima qildik:

  • t_ctid bilan bir xil ko'rinishi uchun indeks raqamiga nol qo'shildi: (sahifa raqami, indeks raqami).
  • lp_flags ko'rsatgichining holati shifrlangan. Bu erda "normal" - bu ko'rsatgich aslida satr versiyasiga ishora qiladi degan ma'noni anglatadi. Boshqa ma'nolarni keyinroq ko'rib chiqamiz.
  • Barcha ma'lumot bitlaridan hozirgacha faqat ikkita juftlik aniqlangan. Xmin_committed va xmin_aborted bitlari xmin tranzaksiya raqami bajarilganligini (bekor qilinganligini) bildiradi. Ikki o'xshash bit xmax tranzaksiya raqamiga ishora qiladi.

Biz nimani ko'ramiz? Qatorni kiritganingizda, jadval sahifasida qatorning birinchi va yagona versiyasiga ishora qiluvchi 1-raqamli indeks paydo bo'ladi.

String versiyasida xmin maydoni joriy tranzaksiya raqami bilan to'ldiriladi. Tranzaktsiya hali ham faol, shuning uchun xmin_committed va xmin_aborted bitlari o'rnatilmagan.

Qator versiyasi ctid maydoni bir xil qatorga ishora qiladi. Bu yangiroq versiya mavjud emasligini anglatadi.

Xmax maydoni 0 raqami bilan to'ldirilgan, chunki qatorning ushbu versiyasi o'chirilmagan va joriy. Tranzaksiyalar bu raqamga e'tibor bermaydi, chunki xmax_aborted biti o'rnatilgan.

Keling, tranzaksiya raqamlariga ma'lumot bitlarini qo'shish orqali o'qishni yaxshilash yo'lida yana bir qadam tashlaylik. Va funksiya yarataylik, chunki so'rov bizga bir necha marta kerak bo'ladi:

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

Ushbu shaklda, satr versiyasining sarlavhasida nima sodir bo'layotgani aniqroq bo'ladi:

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

Shunga o'xshash, ammo sezilarli darajada kamroq batafsil ma'lumotni xmin va xmax psevdo-ustunlari yordamida jadvalning o'zidan olish mumkin:

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

Fiksatsiya

Agar tranzaktsiya muvaffaqiyatli yakunlangan bo'lsa, siz uning holatini eslab qolishingiz kerak - u amalga oshirilganligiga e'tibor bering. Buning uchun XACT deb nomlangan struktura ishlatiladi (va 10-versiyadan oldin u CLOG (commit log) deb nomlangan va bu nom hali ham turli joylarda topilishi mumkin).

XACT tizim katalog jadvali emas; bu PGDATA/pg_xact katalogidagi fayllar. Ularda har bir tranzaksiya uchun ikkita bit ajratilgan: bajarilgan va bekor qilingan - xuddi qator versiyasi sarlavhasidagi kabi. Ushbu ma'lumot faqat qulaylik uchun bir nechta fayllarga bo'lingan, biz muzlatish haqida o'ylaganimizda bu masalaga qaytamiz. Va bu fayllar bilan ishlash boshqalar kabi sahifama-sahifa amalga oshiriladi.

Shunday qilib, XACTda tranzaksiya amalga oshirilganda, ushbu tranzaksiya uchun tuzilgan bit o'rnatiladi. Va bularning barchasi bajarilish paytida sodir bo'ladi (garchi biz hali oldindan yozib olish jurnali haqida gapirmayapmiz).

Boshqa tranzaksiya biz ko'rib chiqqan jadval sahifasiga kirganda, u bir nechta savollarga javob berishi kerak bo'ladi.

  1. Xmin tranzaksiyasi tugallandimi? Agar yo'q bo'lsa, u holda satrning yaratilgan versiyasi ko'rinmasligi kerak.
    Ushbu tekshirish namunaning umumiy xotirasida joylashgan va ProcArray deb ataladigan boshqa strukturaga qarash orqali amalga oshiriladi. U barcha faol jarayonlarning ro'yxatini o'z ichiga oladi va ularning har biri uchun joriy (faol) tranzaksiya raqami ko'rsatiladi.
  2. Agar tugallangan bo'lsa, qanday qilib - majburiyat yoki bekor qilish orqali? Agar bekor qilinsa, qator versiyasi ham ko'rinmasligi kerak.
    Bu aynan XACT uchun mo'ljallangan. Biroq, XACT ning oxirgi sahifalari operativ xotirada buferlarda saqlangan bo'lsa-da, XACTni har safar tekshirish qimmat. Shuning uchun, tranzaktsiya holati aniqlangandan so'ng, u string versiyasining xmin_committed va xmin_aborted bitlariga yoziladi. Agar ushbu bitlardan biri o'rnatilgan bo'lsa, u holda xmin tranzaksiya holati ma'lum deb hisoblanadi va keyingi tranzaksiya XACTga kirishi shart emas.

Nima uchun bu bitlar qo'shishni amalga oshirayotgan tranzaksiya tomonidan o'rnatilmagan? Qo'shish sodir bo'lganda, tranzaktsiya muvaffaqiyatli bo'ladimi yoki yo'qligini hali bilmaydi. Va majburiyatni bajarish paytida, qaysi sahifalar qaysi satrlarda o'zgartirilganligi endi aniq emas. Bunday sahifalar ko'p bo'lishi mumkin va ularni yodlash foydasizdir. Bundan tashqari, ba'zi sahifalar bufer keshidan diskka chiqarilishi mumkin; Bitlarni o'zgartirish uchun ularni qayta o'qish majburiyatni sezilarli darajada sekinlashtiradi.

Tejamkorlikning salbiy tomoni shundaki, o'zgarishlardan so'ng har qanday tranzaksiya (hatto oddiy o'qishni amalga oshiradigan - SELECT) bufer keshidagi ma'lumotlar sahifalarini o'zgartirishni boshlashi mumkin.

Shunday qilib, keling, o'zgarishni tuzataylik.

=> COMMIT;

Sahifada hech narsa o'zgarmadi (lekin biz bilamizki, tranzaksiya holati XACTda allaqachon qayd etilgan):

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

Endi sahifaga birinchi bo'lib kirgan tranzaksiya xmin tranzaksiya holatini aniqlashi va uni ma'lumot bitlariga yozishi kerak bo'ladi:

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

O'chir

Qator o'chirilganda, joriy o'chirish operatsiyasining raqami joriy versiyaning xmax maydoniga yoziladi va xmax_aborted biti tozalanadi.

E'tibor bering, faol tranzaksiyaga mos keladigan xmax o'rnatilgan qiymati qatorni blokirovka qilish vazifasini bajaradi. Agar boshqa tranzaksiya bu qatorni yangilamoqchi yoki oΚ»chirmoqchi boΚ»lsa, u xmax tranzaksiya tugashini kutishga majbur boΚ»ladi. Bloklash haqida keyinroq gaplashamiz. Hozircha biz faqat qator qulflari soni cheksiz ekanligini ta'kidlaymiz. Ular RAMda joy egallamaydi va tizimning ishlashi ularning sonidan zarar ko'rmaydi. To'g'ri, "uzoq" tranzaktsiyalarning boshqa kamchiliklari bor, lekin keyinroq bu haqda ko'proq.

Keling, chiziqni o'chiraylik.

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

Biz tranzaksiya raqami xmax maydonida yozilganligini ko'ramiz, lekin ma'lumot bitlari o'rnatilmagan:

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

bekor qilish

O'zgarishlarni bekor qilish xuddi shunday ishlaydi, faqat XACTda tranzaksiya uchun bekor qilingan bit o'rnatiladi. Bekor qilish xuddi shunday tezdir. Buyruq ROLLBACK deb nomlangan bo'lsa-da, o'zgarishlar orqaga qaytarilmaydi: ma'lumotlar sahifalarida tranzaksiya o'zgartirishga muvaffaq bo'lgan hamma narsa o'zgarishsiz qoladi.

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

Sahifaga kirganda, holat tekshiriladi va xmax_aborted maslahat biti qator versiyasiga o'rnatiladi. Xmax raqamining o'zi sahifada qoladi, lekin hech kim unga qaramaydi.

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

yangilash

Yangilanish avval qatorning joriy versiyasini o'chirib tashlagan va keyin yangisini qo'shgandek ishlaydi.

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

So'rov bitta qatorni ishlab chiqaradi (yangi versiya):

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

Ammo sahifada biz ikkala versiyani ham ko'ramiz:

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

O'chirilgan versiya xmax maydonida joriy tranzaksiya raqami bilan belgilanadi. Bundan tashqari, oldingi bitim bekor qilinganligi sababli, bu qiymat eskisining ustiga yoziladi. Va xmax_aborted biti tozalanadi, chunki joriy tranzaksiya holati hali ma'lum emas.

Qatorning birinchi versiyasi endi ikkinchisini (t_ctid maydoni) yangisi sifatida ko'rsatadi.

Indeks sahifasida ikkinchi indeks paydo bo'ladi va ikkinchi qator jadval sahifasidagi ikkinchi versiyaga havola qiladi.

Xuddi o'chirishda bo'lgani kabi, qatorning birinchi versiyasidagi xmax qiymati qator qulflanganligini ko'rsatadi.

Xo'sh, keling, tranzaksiyani yakunlaymiz.

=> COMMIT;

Ko'rsatkichlar

Hozircha biz faqat jadval sahifalari haqida gaplashdik. Indekslar ichida nima sodir bo'ladi?

Indeks sahifalaridagi ma'lumotlar indeksning o'ziga xos turiga qarab katta farq qiladi. Va hatto bir turdagi indeks turli xil sahifalarga ega. Misol uchun, B-daraxtda metadata sahifasi va "muntazam" sahifalar mavjud.

Biroq, sahifada odatda qatorlar va satrlarning o'ziga (xuddi jadval sahifasi kabi) ko'rsatgichlar majmuasi mavjud. Bundan tashqari, sahifaning oxirida maxsus ma'lumotlar uchun joy mavjud.

Indekslardagi qatorlar indeks turiga qarab juda xilma-xil tuzilishga ega bo'lishi mumkin. Masalan, B-daraxt uchun barg sahifalari bilan bog'liq qatorlar indekslash kaliti qiymatini va mos keladigan jadval qatoriga havolani (ctid) o'z ichiga oladi. Umuman olganda, indeks butunlay boshqacha tarzda tuzilishi mumkin.

Eng muhimi shundaki, har qanday turdagi indekslarda qator versiyalari mavjud emas. Xo'sh, yoki biz har bir satr aynan bitta versiya bilan ifodalangan deb taxmin qilishimiz mumkin. Boshqacha qilib aytganda, indeks satrining sarlavhasida xmin va xmax maydonlari mavjud emas. Indeksdagi havolalar qatorlarning barcha jadval versiyalariga olib keladi deb taxmin qilishimiz mumkin - shuning uchun tranzaktsiya qaysi versiyani ko'rishini faqat jadvalga qarab aniqlashingiz mumkin. (Har doimgidek, bu butun haqiqat emas. Ba'zi hollarda ko'rinish xaritasi jarayonni optimallashtirishi mumkin, ammo biz buni keyinroq batafsil ko'rib chiqamiz.)

Shu bilan birga, indeks sahifasida biz ikkala versiyaga ham ko'rsatgichlarni topamiz, ham joriy, ham eski:

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

Virtual operatsiyalar

Amalda, PostgreSQL tranzaksiya raqamlarini "saqlash" imkonini beruvchi optimallashtirishlardan foydalanadi.

Agar tranzaktsiya faqat ma'lumotlarni o'qisa, u qator versiyalarining ko'rinishiga ta'sir qilmaydi. Shuning uchun, xizmat ko'rsatish jarayoni birinchi navbatda tranzaktsiyaga virtual xid beradi. Raqam jarayon identifikatori va tartib raqamidan iborat.

Ushbu raqamni berish barcha jarayonlar o'rtasida sinxronizatsiyani talab qilmaydi va shuning uchun juda tezdir. Biz muzlatish haqida gapirganda virtual raqamlardan foydalanishning yana bir sababi bilan tanishamiz.

Ma'lumotlarning suratlarida virtual raqamlar hech qanday tarzda hisobga olinmaydi.

Vaqtning turli nuqtalarida tizimda allaqachon ishlatilgan raqamlar bilan virtual tranzaktsiyalar bo'lishi mumkin va bu normaldir. Ammo bunday raqamni ma'lumotlar sahifalariga yozib bo'lmaydi, chunki keyingi safar sahifaga kirishda u barcha ma'nosini yo'qotishi mumkin.

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

Agar tranzaksiya ma'lumotlarni o'zgartira boshlasa, unga haqiqiy, noyob tranzaksiya raqami beriladi.

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

=> COMMIT;

O'rnatilgan operatsiyalar

Ballarni saqlang

SQL da aniqlangan ballarni saqlang (saqlash nuqtasi), bu sizga tranzaktsiyaning bir qismini butunlay to'xtatmasdan bekor qilish imkonini beradi. Ammo bu yuqoridagi diagrammaga mos kelmaydi, chunki tranzaktsiya barcha o'zgarishlar uchun bir xil maqomga ega va jismoniy jihatdan hech qanday ma'lumot qaytarilmaydi.

Ushbu funksiyani amalga oshirish uchun saqlash nuqtasi bilan tranzaksiya bir nechta alohida qismlarga bo'linadi ichki o'rnatilgan operatsiyalar (subtransaktsiya), uning holati alohida boshqarilishi mumkin.

Ichki tranzaktsiyalar o'z raqamiga ega (asosiy tranzaksiya sonidan yuqori). Ichki o'rnatilgan tranzaktsiyalar holati XACTda odatiy tarzda qayd etiladi, ammo yakuniy holat asosiy tranzaksiya holatiga bog'liq: agar u bekor qilingan bo'lsa, u holda barcha ichki o'rnatilgan tranzaktsiyalar ham bekor qilinadi.

Tranzaktsiyalarni joylashtirish haqidagi ma'lumotlar PGDATA/pg_subtrans katalogidagi fayllarda saqlanadi. Fayllarga XACT buferlari bilan bir xil tarzda tashkil etilgan misolning umumiy xotirasidagi buferlar orqali kirish mumkin.

Ichki tranzaksiyalarni avtonom tranzaksiyalar bilan aralashtirib yubormang. Avtonom tranzaktsiyalar hech qanday tarzda bir-biriga bog'liq emas, lekin ichki o'rnatilgan operatsiyalar. Oddiy PostgreSQL-da avtonom tranzaktsiyalar mavjud emas va, ehtimol, eng yaxshisi: ular juda kamdan-kam hollarda kerak bo'ladi va ularning boshqa ma'lumotlar bazasida mavjudligi suiiste'mollikni keltirib chiqaradi, bundan keyin hamma azoblanadi.

Keling, jadvalni tozalaymiz, tranzaktsiyani boshlaymiz va qatorni kiritamiz:

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

Endi saqlash nuqtasini qo'yamiz va boshqa qatorni kiritamiz.

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

Esda tutingki, txid_current() funksiyasi ichki tranzaksiya raqamini emas, balki asosiy tranzaksiya raqamini qaytaradi.

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

Saqlash nuqtasiga qaytaylik va uchinchi qatorni kiritamiz.

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

Sahifada biz bekor qilingan ichki tranzaksiya tomonidan qo'shilgan qatorni ko'rishni davom ettiramiz.

Biz o'zgarishlarni tuzatamiz.

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

Endi siz har bir ichki o'rnatilgan tranzaksiya o'z maqomiga ega ekanligini aniq ko'rishingiz mumkin.

E'tibor bering, ichki o'rnatilgan tranzaksiyalarni SQLda aniq ishlatib bo'lmaydi, ya'ni joriy operatsiyani tugatmasdan turib yangi tranzaksiyani boshlay olmaysiz. Ushbu mexanizm saqlash nuqtalaridan foydalanganda, shuningdek, PL/pgSQL istisnolarini ko'rib chiqishda va boshqa bir qator ekzotik holatlarda bevosita faollashadi.

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

Xatolar va operatsiyalarning atomikligi

Agar operatsiyani bajarishda xatolik yuzaga kelsa nima bo'ladi? Masalan, bu kabi:

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

Xato ro'y berdi. Endi tranzaktsiya bekor qilingan deb hisoblanadi va unda hech qanday operatsiyalarga ruxsat berilmaydi:

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

Va agar siz o'zgarishlarni amalga oshirishga harakat qilsangiz ham, PostgreSQL bekor qilish haqida xabar beradi:

=> COMMIT;
ROLLBACK

Nega tranzaksiya muvaffaqiyatsizlikdan keyin davom eta olmaydi? Haqiqat shundaki, biz o'zgarishlarning bir qismiga kirish huquqiga ega bo'lgan xatolik yuzaga kelishi mumkin - hatto tranzaktsiyaning atomikligi, lekin operator buzilgan bo'lar edi. Bizning misolimizda bo'lgani kabi, operator xatolikdan oldin bitta qatorni yangilashga muvaffaq bo'ldi:

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

Aytish kerakki, psql-da xato operatorning harakatlari orqaga qaytarilgandek, muvaffaqiyatsizlikdan keyin ham operatsiyani davom ettirishga imkon beradigan rejim mavjud.

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

Taxmin qilish qiyin emas, bu rejimda psql aslida har bir buyruq oldiga yashirin saqlash nuqtasini qo'yadi va muvaffaqiyatsiz bo'lsa, unga qaytishni boshlaydi. Ushbu rejim sukut bo'yicha ishlatilmaydi, chunki saqlash nuqtalarini o'rnatish (hatto ularga qaytmasdan ham) katta xarajatlarni talab qiladi.

Davomi.

Manba: www.habr.com

a Izoh qo'shish