MVCC-3. Стринг версиялары

Ошентип, биз тиешелүү маселелерди карап чыктык изоляция, жана жөнүндө чегинүү жасады маалыматтарды төмөнкү деңгээлде уюштуруу. Акыр-аягы, биз эң кызыктуу бөлүгүнө - сап версияларына келдик.

баш

Жогоруда айтылгандай, ар бир сап бир эле учурда бир нече версияда маалымат базасында болушу мүмкүн. Бир версияны экинчисинен кандайдыр бир түрдө айырмалоо керек.Бул үчүн ар бир версияда бул версиянын аракетинин “убакыт”ын аныктоочу эки белги бар (xmin жана xmax). Тырмакчаларда - анткени ал колдонулган убакыт эмес, бирок атайын өсүүчү эсептегич. Жана бул эсептегич транзакциянын номери.

(Адаттагыдай эле, чындык татаалыраак: транзакциянын саны эсептегичтин чектелген бит сыйымдуулугунан улам ар дайым көбөйө албайт. Бирок биз тоңуп калганда бул деталдарды майда-чүйдөсүнө чейин карап чыгабыз.)

Катар түзүлгөндө, xmin INSERT буйругун чыгарган транзакциянын номерине коюлат жана xmax бош калат.

Катар жок кылынганда, учурдагы версиянын xmax мааниси DELETE аткарган транзакциянын номери менен белгиленет.

Качан катар UPDATE буйругу менен өзгөртүлгөндө, чындыгында эки операция аткарылат: DELETE жана INSERT. Саптын учурдагы версиясы UPDATE аткарган транзакциянын санына барабар xmax коет. Андан кийин ошол эле саптын жаңы версиясы түзүлөт; анын xmin мааниси мурунку версиянын xmax маанисине дал келет.

xmin жана xmax талаалары сап версиясынын аталышында камтылган. Бул талаалардан тышкары, баш маалымат башкаларды камтыйт, мисалы:

  • infomask бул версиянын касиеттерин аныктаган биттердин сериясы. Алардын саны абдан көп; Биз акырындык менен негизгилерин карап чыгабыз.
  • ctid - ошол эле саптын кийинки, жаңыраак версиясына шилтеме. Саптын эң жаңы, эң учурдагы версиясы үчүн ctid ушул версиянын өзүнө тиешелүү. Сандын (x,y) формасы бар, мында х - беттин номери, у - массивдеги индекстин номери.
  • null битмап - берилген версиянын нөл маанисин (NULL) камтыган тилкелерин белгилейт. NULL кадимки маалымат түрүнүн маанилеринин бири эмес, андыктан атрибут өзүнчө сакталышы керек.

Натыйжада, баш аты кыйла чоң - саптын ар бир версиясы үчүн кеминде 23 байт, ал эми адатта NULL битмаптын эсебинен көбүрөөк. Эгерде таблица "тар" болсо (башкача айтканда, бир нече мамычаларды камтыса), кошумча чыгымдар пайдалуу маалыматтан көбүрөөк болушу мүмкүн.

коюу

Кыстаруудан баштап, төмөнкү деңгээлдеги сап операциялары кандай аткарыларын кененирээк карап чыгалы.

Эксперимент үчүн, келгиле, эки мамычасы жана алардын биринде индекси бар жаңы таблица түзөлү:

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

Транзакцияны баштагандан кийин бир катар киргизели.

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

Бул жерде биздин учурдагы транзакциянын номери:

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

Келгиле, баракчанын мазмунун карап көрөлү. Pageinspect кеңейтүүсүнүн heap_page_items функциясы көрсөткүчтөр жана сап версиялары жөнүндө маалымат алууга мүмкүндүк берет:

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

PostgreSQLдеги үймөк деген сөз таблицаларга тиешелүү экенине көңүл буруңуз. Бул терминдин дагы бир кызыктай колдонулушу - үймөк белгилүү маалымат структурасы, таблица менен эч кандай жалпылыгы жок. Бул жерде сөз иреттүү көрсөткүчтөрдөн айырмаланып, “бардыгы чогуу ыргытылат” деген мааниде колдонулат.

Функция маалыматтарды "кандай болсо, ошондой" түшүнүү кыйын форматта көрсөтөт. Аны түшүнүү үчүн биз маалыматтын бир бөлүгүн гана калтырып, аны чечмелейбиз:

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

Бул жерде биз эмне кылдык:

  • t_ctid менен окшош болушу үчүн индекстин номерине нөл кошулду: (беттин номери, индекс номери).
  • lp_flags көрсөткүчүнүн абалы чечмеленди. Бул жерде ал "нормалдуу" - бул көрсөткүч чындыгында саптын версиясын билдирет дегенди билдирет. Башка маанилерди кийинчерээк карайбыз.
  • Маалыматтык биттердин ичинен азырынча эки түгөй гана аныкталган. xmin_committed жана xmin_aborted биттери xmin транзакция номеринин аткарылганын (токтурулганын) көрсөтөт. Эки окшош бит xmax транзакция номерин билдирет.

Биз эмнени көрүп жатабыз? Сиз сапты киргизгениңизде таблица бетинде катардын биринчи жана жалгыз версиясын көрсөткөн №1 индекс пайда болот.

Сап версиясында xmin талаасы учурдагы транзакциянын номери менен толтурулат. Транзакция дагы эле активдүү, андыктан xmin_committed жана xmin_aborted биттери коюлган эмес.

Катар версиясынын ctid талаасы ошол эле сапка тиешелүү. Бул жаңыраак версия жок дегенди билдирет.

xmax талаасы 0 номуру менен толтурулган, анткени катардын бул версиясы жок кылынган эмес жана учурдагы. Транзакциялар бул санга көңүл бурбайт, анткени xmax_aborted бит коюлган.

Келгиле, транзакциялардын номерлерине маалымат биттерин кошуу менен окулушун жакшыртууга дагы бир кадам жасайлы. Келгиле, функцияны түзөлү, анткени бизге суроо-талап бир нече жолу керек болот:

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

Бул формада катардын версиясынын баш жагында эмне болуп жатканы айкыныраак болот:

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

Окшош, бирок анча деталдуу эмес маалымат xmin жана xmax псевдо-мамычаларын колдонуп, таблицадан алса болот:

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

Фиксация

Эгерде транзакция ийгиликтүү аяктаса, анын статусун эстеп калуу керек - бул жасалгандыгын белгилеңиз. Бул үчүн, XACT деп аталган структура колдонулат (жана 10-версияга чейин ал CLOG (commit log) деп аталып келген жана бул аталыш дагы эле ар кайсы жерлерде кездешет).

XACT системалык каталог таблицасы эмес; бул PGDATA/pg_xact каталогундагы файлдар. Аларда ар бир транзакция үчүн эки бит бар: аткарылган жана токтотулган - катар версиясынын аталышындагыдай. Бул маалымат ыңгайлуулук үчүн гана бир нече файлдарга бөлүнгөн, биз тоңдурууну караганыбызда бул маселеге кайрылабыз. Жана бул файлдар менен иштөө башкалар сыяктуу эле барактан-бетке жүргүзүлөт.

Ошентип, XACTте транзакция жасалганда, бул транзакция үчүн жасалган бит коюлат. Жана мунун баары жасоо учурунда болот (бирок биз азырынча алдын ала жазуу журналы жөнүндө сөз кылбайбыз).

Башка транзакция биз карап чыккан таблица барагына киргенде, ал бир нече суроолорго жооп бериши керек болот.

  1. xmin транзакциясы аяктадыбы? Эгерде андай болбосо, анда саптын түзүлгөн версиясы көрүнбөшү керек.
    Бул текшерүү инстанциянын жалпы эсинде жайгашкан жана ProcArray деп аталган башка структураны карап чыгуу менен жүзөгө ашырылат. Ал бардык активдүү процесстердин тизмесин камтыйт жана ар бири үчүн анын учурдагы (активдүү) транзакциясынын номери көрсөтүлөт.
  2. Эгер бүтсө, анда кантип - жасоо же жокко чыгаруу менен? Эгер жокко чыгарылса, сап версиясы да көрүнбөшү керек.
    XACT дал ушул нерсе үчүн. Бирок, XACTтин акыркы барактары оперативдүү эс тутумдун буферлеринде сакталганы менен, XACT ар бир жолу текшерип туруу кымбатка турат. Ошондуктан, транзакция статусу аныкталгандан кийин, ал сап версиясынын xmin_committed жана xmin_aborted биттерине жазылат. Эгерде бул биттердин бири коюлса, анда xmin транзакциясынын абалы белгилүү болуп эсептелет жана кийинки транзакция XACTке кирүүгө мажбур болбойт.

Эмне үчүн бул биттерди транзакциянын өзү киргизбейт? Кыстаруу пайда болгондо, транзакция ийгиликтүү болобу же жокпу билбейт. Ал эми милдеттендирилип жаткан учурда кайсы саптарда кайсы барактар ​​өзгөртүлгөнү так эмес. Мындай баракчалар көп болушу мүмкүн, аларды жаттоо пайдасыз. Мындан тышкары, кээ бир баракчаларды буфердик кэштен дискке чыгарууга болот; биттерди өзгөртүү үчүн аларды кайра окуу, милдеттенмени кыйла жайлатмак.

Сактоолордун терс жагы өзгөртүүлөрдөн кийин ар кандай транзакция (жөнөкөй окууну да аткарса да - SELECT) буфердик кэштеги маалымат барактарын өзгөртө башташы мүмкүн.

Андыктан, келгиле, өзгөртүүнү оңдойлу.

=> COMMIT;

Баракта эч нерсе өзгөргөн жок (бирок биз транзакциянын абалы XACTта мурунтан эле жазылганын билебиз):

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

Эми баракчага биринчи кирген транзакция xmin транзакциясынын статусун аныктап, аны маалымат биттерине жазышы керек:

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

алып салуу

Катар жок кылынганда, учурдагы жок кылуу транзакциясынын номери учурдагы версиянын xmax талаасына жазылат жана xmax_aborted бит тазаланат.

Активдүү транзакцияга туура келген xmax белгиленген мааниси катардын кулпусу катары иштээрин эске алыңыз. Эгер башка транзакция бул катарды жаңырткысы келсе же жок кылгысы келсе, ал xmax транзакциясынын аягына чыгышын күтүүгө аргасыз болот. Бөгөттөө жөнүндө кийинчерээк сүйлөшөбүз. Азырынча, биз жөн гана катар кулпуларынын саны чексиз экенин белгилейбиз. Алар оперативдик эстутумда орун ээлебейт жана системанын иштеши алардын санына зыян келтирбейт. Ырас, "узун" бүтүмдөрдүн башка кемчиликтери бар, бирок бул жөнүндө кийинчерээк.

Келгиле, сызыкты өчүрөлү.

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

Биз транзакциянын номери xmax талаасында жазылганын көрөбүз, бирок маалымат биттери коюлган эмес:

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

жокко чыгаруу

Өзгөртүүлөрдү токтотуу, жасоого окшош иштейт, XACTте гана транзакция үчүн токтотулган бит коюлат. Жок кылуу милдеттендиргендей тез. Буйрук ROLLBACK деп аталса да, өзгөртүүлөр артка кайтарылбайт: маалымат беттеринде транзакция өзгөртө алган нерселердин баары өзгөрүүсүз бойдон калууда.

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

Баракка киргенде, абал текшерилет жана xmax_aborted кыйытма бит сап версиясына коюлат. xmax саны өзү баракта калат, бирок эч ким аны карабайт.

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

өзгөртүү

Жаңыртуу алгач катардын учурдагы версиясын өчүрүп, андан кийин жаңысын киргизгендей иштейт.

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

Суроо бир сапты чыгарат (жаңы версия):

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

Бирок баракта биз эки версияны көрөбүз:

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

Жок кылынган версия xmax талаасында учурдагы транзакциянын номери менен белгиленет. Мындан тышкары, мурунку транзакция жокко чыгарылгандыктан, бул маани эскинин үстүнө жазылган. Жана xmax_aborted бит тазаланат, анткени учурдагы транзакциянын абалы азырынча белгисиз.

Саптын биринчи версиясы экинчисин (t_ctid талаасы) жаңысы катары билдирет.

Индекс барагында экинчи индекс пайда болот, ал эми экинчи сап таблица бетиндеги экинчи версияга шилтеме кылат.

Жок кылуудагыдай эле, катардын биринчи версиясындагы xmax мааниси саптын кулпуланганын көрсөтөт.

Келгиле, транзакцияны бүтүрөлү.

=> COMMIT;

көрсөткүчтөрү

Буга чейин биз таблица беттери жөнүндө гана сүйлөштүк. Индекстердин ичинде эмне болот?

Индекс беттериндеги маалымат индекстин конкреттүү түрүнө жараша абдан өзгөрүп турат. Ал эми индекстин бир түрү да ар кандай баракчаларга ээ. Мисалы, В дарагында метаберилиштер барагы жана "кадимки" барактар ​​бар.

Бирок, барак, адатта, саптарга жана саптардын өздөрүнө көрсөткүчтөрдүн массивине ээ (таблица барагы сыяктуу). Мындан тышкары, барактын аягында атайын маалыматтар үчүн орун бар.

Индекстердин саптары да индекстин түрүнө жараша ар түрдүү структураларга ээ болушу мүмкүн. Мисалы, B-дарагы үчүн жалбырак барактарына тиешелүү саптар индекстөө ачкыч маанисин жана тиешелүү таблица сапына шилтемени (ctid) камтыйт. Жалпысынан алганда, индекс такыр башка жол менен түзүлүшү мүмкүн.

Эң негизгиси, ар кандай түрдөгү индекстерде катар версиялары жок. Ооба, же биз ар бир сызык так бир версия менен берилген деп болжолдоого болот. Башка сөз менен айтканда, индекс сап башында xmin жана xmax талаалары жок. Индекстеги шилтемелер саптардын бардык таблица версияларына алып барат деп ойлойбуз - ошондуктан транзакция кайсы версияны көрөрүн таблицага карап гана биле аласыз. (Адаттагыдай эле, бул бүт чындык эмес. Кээ бир учурларда, көрүнүү картасы процессти оптималдаштыра алат, бирок биз муну кийинчерээк кененирээк карап чыгабыз.)

Ошол эле учурда, индекс барагында биз учурдагы жана эски эки версияга тең көрсөткүчтөрдү табабыз:

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

Виртуалдык транзакциялар

Практикада PostgreSQL транзакциялардын номерлерин “сактоого” мүмкүндүк берген оптималдаштырууларды колдонот.

Эгерде транзакция маалыматтарды гана окуса, ал сап версияларынын көрүнүүсүнө эч кандай таасир этпейт. Ошондуктан, тейлөө процесси биринчи транзакцияга виртуалдык xid берет. Номер процесстин идентификаторунан жана катар номеринен турат.

Бул номерди берүү бардык процесстердин ортосунда синхрондоштурууну талап кылбайт жана ошондуктан абдан тез. Виртуалдык сандарды колдонуунун дагы бир себеби менен тоңуу тууралуу сөз болгондо таанышабыз.

Виртуалдык сандар маалымат снапшоттарында эч кандай түрдө эске алынбайт.

Убакыттын ар кандай учурларында системада мурунтан эле колдонулган сандар менен виртуалдык транзакциялар болушу мүмкүн жана бул нормалдуу көрүнүш. Бирок мындай санды маалымат барактарына жазуу мүмкүн эмес, анткени кийинки жолу баракка киргенде ал бардык маанисин жоготуп коюшу мүмкүн.

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

Эгерде транзакция маалыматтарды өзгөртө баштаса, ага реалдуу, уникалдуу транзакция номери берилет.

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

=> COMMIT;

Ички транзакциялар

Упайларды сактаңыз

SQLде аныкталган упайларды сактоо (сактоо пункту), ал транзакциянын бир бөлүгүн толугу менен үзгүлтүксүз жокко чыгарууга мүмкүндүк берет. Бирок бул жогорудагы диаграммага туура келбейт, анткени транзакция бардык өзгөртүүлөр үчүн бирдей статуска ээ жана физикалык жактан эч кандай маалымат артка кайтарылбайт.

Бул функцияны ишке ашыруу үчүн сактоо пункту менен транзакция бир нече өзүнчө бөлүнөт уяланган транзакциялар (субтранзакция), статусу өзүнчө башкарылышы мүмкүн.

Уюшкан транзакциялардын өзүнүн номери бар (негизги транзакциянын санынан жогору). Уюшкан транзакциялардын статусу XACTта кадимки жол менен жазылат, бирок акыркы статус негизги транзакциянын абалына жараша болот: эгерде ал жокко чыгарылса, анда бардык уяча транзакциялар да жокко чыгарылат.

Транзакция уясы жөнүндө маалымат PGDATA/pg_subtrans каталогундагы файлдарда сакталат. Файлдарга XACT буферлери сыяктуу уюштурулган инстанциянын жалпы эс тутумундагы буферлер аркылуу жетүүгө болот.

Уюшкан транзакцияларды автономдуу транзакциялар менен чаташтырбаңыз. Автономдуу транзакциялар бири-биринен көз каранды эмес, бирок ички транзакциялар. Кадимки PostgreSQLде автономдуу транзакциялар жок, жана, балким, эң жакшысы үчүн: алар абдан, өтө сейрек керектелет жана алардын башка DBMSларда болушу кыянаттыкты жаратат, андан кийин бардыгы жабыркайт.

Келгиле, таблицаны тазалап, транзакцияны баштайлы жана сапты киргизели:

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

Эми сактоо чекитин коюп, дагы бир сапты салалы.

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

txid_current() функциясы ички транзакциянын номерин эмес, негизги транзакциянын номерин кайтарарын эске алыңыз.

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

Сактоо чекитине кайтып келип, үчүнчү сапты киргизели.

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

Баракта биз жокко чыгарылган ички транзакция менен кошулган сапты көрүүнү улантабыз.

Биз өзгөртүүлөрдү оңдойбуз.

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

Эми ар бир ички транзакциянын өзүнүн статусу бар экенин айкын көрө аласыз.

Көңүл буруңуз, уя салынган транзакцияларды SQLде ачык колдонуу мүмкүн эмес, башкача айтканда, учурдагы транзакцияны аягына чыгарбастан жаңы транзакцияны баштоого болбойт. Бул механизм сактоо чекиттерин колдонууда, ошондой эле PL/pgSQL өзгөчөлүктөрүн иштетүүдө жана башка бир катар экзотикалык учурларда кыйыр түрдө иштетилет.

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

Операциялардын каталары жана атомдуулугу

Операцияны аткарып жатканда ката кетсе эмне болот? Мисалы, бул сыяктуу:

=> 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;
ERROR:  current transaction is aborted, commands ignored until end of transaction block

Жана сиз өзгөртүүлөрдү киргизүүгө аракет кылсаңыз да, PostgreSQL үзгүлтүккө учуратууну билдирет:

=> COMMIT;
ROLLBACK

Эмне үчүн транзакция ийгиликсиз болгондон кийин улана албайт? Чындыгында ката биз өзгөрүүлөрдүн бир бөлүгүнө кирүү мүмкүнчүлүгүнө ээ боло тургандай пайда болушу мүмкүн - бүтүмдүн атомдуулугу, бирок оператор бузулат. Биздин мисалдагыдай, оператор катага чейин бир сапты жаңырта алган:

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

Айта кетчү нерсе, psql ката оператордун аракеттери артка кайтарылгандай транзакцияны улантууга мүмкүндүк берген режимге ээ.

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

Бул режимде psql чындыгында ар бир буйруктун алдына имплициттүү сактоо чекитин коет, ал эми иштебей калган учурда ага артка кайтарууну баштоону болжолдоо кыйын эмес. Бул режим демейки боюнча колдонулбайт, анткени сактоо чекиттерин коюу (аларга кайра жылбай туруп) олуттуу чыгымдарды талап кылат.

Уландысы.

Source: www.habr.com

Комментарий кошуу