MVCC-3. Стринг хувилбарууд

Тиймээс бид холбогдох асуудлыг авч үзсэн тусгаарлагч, мөн тухай ухралт хийсэн өгөгдлийг бага түвшинд зохион байгуулах. Эцэст нь бид хамгийн сонирхолтой хэсэг болох мөрт хувилбарууд руу орлоо.

Толгой

Өмнө дурьдсанчлан мөр бүр мэдээллийн санд хэд хэдэн хувилбарт нэгэн зэрэг байж болно. Нэг хувилбар нь нөгөөгөөсөө ямар нэгэн байдлаар ялгагдах ёстой.Үүний тулд хувилбар бүр энэ хувилбарын үйл ажиллагааны "цаг"-ыг (xmin ба xmax) тодорхойлдог хоёр тэмдэгтэй байна. Хашилтанд - учир нь энэ нь цаг хугацаа биш, харин өсөн нэмэгдэж буй тусгай тоолуур юм. Мөн энэ тоолуур бол гүйлгээний дугаар юм.

(Ердийнх шиг, бодит байдал илүү төвөгтэй байдаг: тоолуурын хязгаарлагдмал хүчин чадалтай тул гүйлгээний дугаар байнга нэмэгдэх боломжгүй. Гэхдээ бид хөлдөх үед эдгээр дэлгэрэнгүй мэдээллийг нарийвчлан авч үзэх болно.)

Мөр үүсгэх үед INSERT командыг өгсөн гүйлгээний дугаарт xmin тохируулагдсан байх ба xmax хоосон үлдэнэ.

Мөрийг устгах үед одоогийн хувилбарын xmax утгыг УСТГАХ үйлдлийг гүйцэтгэсэн гүйлгээний дугаараар тэмдэглэнэ.

UPDATE командын тусламжтайгаар мөрийг өөрчлөхөд DELETE болон INSERT гэсэн хоёр үйлдэл хийгдэнэ. Мөрийн одоогийн хувилбар нь UPDATE хийсэн гүйлгээний тоотой тэнцүү xmax-ийг тохируулсан. Дараа нь ижил мөрийн шинэ хувилбар үүсгэгдэнэ; түүний xmin утга нь өмнөх хувилбарын xmax утгатай давхцаж байна.

Xmin болон xmax талбарууд нь мөрийн хувилбарын толгой хэсэгт багтсан болно. Эдгээр талбаруудаас гадна толгой хэсэгт бусад зүйлс орно, жишээлбэл:

  • infomask нь энэ хувилбарын шинж чанарыг тодорхойлдог хэд хэдэн бит юм. Тэдгээрийн нэлээд олон нь байдаг; Бид гол зүйлийг аажмаар авч үзэх болно.
  • ctid нь ижил мөрийн дараагийн, шинэ хувилбарын холбоос юм. Мөрийн хамгийн сүүлийн үеийн, хамгийн сүүлийн үеийн хувилбарын хувьд ctid нь энэ хувилбарыг хэлдэг. Тоо нь (x,y) хэлбэртэй, энд x нь хуудасны дугаар, y нь массив дахь индексийн дугаар юм.
  • null bitmap - Өгөгдсөн хувилбарын хоосон утга (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-ийн сүүлийн хуудсууд нь RAM-ийн буферт хадгалагддаг боловч 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 гүйлгээг дуусгахыг хүлээхээс өөр аргагүй болно. Бид дараа нь блоклох талаар дэлгэрэнгүй ярих болно. Одоогийн байдлаар бид мөрний түгжээний тоо хязгааргүй гэдгийг л тэмдэглэж байна. Тэд RAM-д зай эзэлдэггүй бөгөөд системийн гүйцэтгэл нь тэдний тооноос хамаардаггүй. Үнэн, "урт" гүйлгээ нь бусад сул талуудтай боловч дараа нь энэ талаар илүү дэлгэрэнгүй ярих болно.

Мөрийг устгацгаая.

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

Индексүүд

Одоогоор бид зөвхөн хүснэгтийн хуудсуудын талаар ярилцсан. Индекс дотор юу болдог вэ?

Индекс хуудасны мэдээлэл нь тухайн индексийн төрлөөс хамааран ихээхэн ялгаатай байдаг. Мөн нэг төрлийн индекс ч гэсэн өөр өөр төрлийн хуудастай байдаг. Жишээлбэл, В мод нь мета өгөгдлийн хуудас болон "ердийн" хуудастай.

Гэсэн хэдий ч, хуудас нь ихэвчлэн мөр болон мөрүүд рүү чиглэсэн олон тооны заагчтай байдаг (хүснэгт хуудас шиг). Нэмж дурдахад хуудасны төгсгөлд тусгай өгөгдөл оруулах зай бий.

Индекс дэх мөрүүд нь индексийн төрлөөс хамааран өөр өөр бүтэцтэй байж болно. Жишээлбэл, В модны хувьд хуудасны хуудастай холбоотой мөрүүд нь индексжүүлэх түлхүүрийн утгыг агуулж, хүснэгтийн харгалзах мөрийн лавлагааг (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 гаргадаг. Уг дугаар нь процессын ID болон дарааллын дугаараас бүрдэнэ.

Энэ дугаарыг гаргах нь бүх процессуудын хооронд синхрончлол хийх шаардлагагүй тул маш хурдан байдаг. Бид хөлдөх тухай ярихдаа виртуал дугаар ашиглах өөр нэг шалтгаантай танилцах болно.

Өгөгдлийн хормын хувилбарт виртуал тоог ямар ч байдлаар тооцдоггүй.

Цаг хугацааны өөр өөр үед системд аль хэдийн ашиглагдсан тоо бүхий виртуал гүйлгээ байж болох бөгөөд энэ нь хэвийн үзэгдэл юм. Гэхдээ ийм тоог өгөгдлийн хуудсанд бичих боломжгүй, учир нь дараагийн удаа хуудсанд хандахад энэ нь бүх утгаа алдаж магадгүй юм.

=> 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-д тодорхойлсон оноо хэмнэх (savepoint), энэ нь гүйлгээний зарим хэсгийг бүрэн тасалдуулахгүйгээр цуцлах боломжийг олгодог. Гэхдээ энэ нь дээрх диаграмд ​​тохирохгүй, учир нь гүйлгээ нь бүх өөрчлөлтийн хувьд ижил статустай бөгөөд физикийн хувьд ямар ч өгөгдөл буцаагдахгүй.

Энэ функцийг хэрэгжүүлэхийн тулд хадгалах цэг бүхий гүйлгээг хэд хэдэн тусдаа хэсэгт хуваадаг үүрлэсэн гүйлгээ (дэд гүйлгээ), статусыг тусад нь удирдах боломжтой.

Оруулсан гүйлгээ нь өөрийн гэсэн дугаартай (үндсэн гүйлгээний тооноос их). Оруулсан гүйлгээний статусыг 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 нь команд бүрийн өмнө далд хадгалах цэг тавьдаг бөгөөд бүтэлгүйтсэн тохиолдолд түүнийг буцаах ажиллагааг эхлүүлдэг гэдгийг таахад хэцүү биш юм. Хадгалах цэгүүдийг (түүн рүү буцаахгүйгээр) тохируулах нь ихээхэн хэмжээний зардал шаарддаг тул энэ горимыг анхдагч байдлаар ашигладаггүй.

Үргэлжлэл.

Эх сурвалж: www.habr.com

сэтгэгдэл нэмэх