MVCC-3. Versi rentetan

Jadi, kami telah mempertimbangkan isu berkaitan penebat, dan membuat pengunduran tentang menyusun data pada tahap yang rendah. Dan akhirnya kami sampai ke bahagian yang paling menarik - versi rentetan.

Tajuk

Seperti yang telah kami katakan, setiap baris boleh wujud secara serentak dalam beberapa versi dalam pangkalan data. Satu versi mesti entah bagaimana dibezakan daripada yang lain. Untuk tujuan ini, setiap versi mempunyai dua tanda yang menentukan "masa" tindakan versi ini (xmin dan xmax). Dalam petikan - kerana bukan masa sedemikian yang digunakan, tetapi kaunter peningkatan khas. Dan kaunter ini adalah nombor transaksi.

(Seperti biasa, realitinya lebih rumit: nombor urus niaga tidak boleh meningkat sepanjang masa disebabkan kapasiti bit yang terhad pada kaunter. Tetapi kami akan melihat butiran ini secara terperinci apabila kami membekukan.)

Apabila baris dibuat, xmin ditetapkan kepada nombor transaksi yang mengeluarkan arahan INSERT dan xmax dibiarkan kosong.

Apabila baris dipadamkan, nilai xmax versi semasa ditandakan dengan nombor transaksi yang melakukan DELETE.

Apabila baris diubah suai oleh perintah KEMASKINI, dua operasi sebenarnya dilakukan: PADAM dan INSERT. Versi semasa baris menetapkan xmax sama dengan bilangan transaksi yang melakukan KEMASKINI. Versi baharu rentetan yang sama kemudian dibuat; nilai xminnya bertepatan dengan nilai xmax versi sebelumnya.

Medan xmin dan xmax disertakan dalam pengepala versi baris. Sebagai tambahan kepada medan ini, pengepala mengandungi yang lain, sebagai contoh:

  • infomask ialah satu siri bit yang mentakrifkan sifat versi ini. Terdapat agak banyak daripada mereka; Kami secara beransur-ansur akan mempertimbangkan yang utama.
  • ctid ialah pautan ke versi seterusnya yang lebih baharu bagi baris yang sama. Untuk versi rentetan yang terbaharu dan terkini, ctid merujuk kepada versi ini sendiri. Nombor tersebut mempunyai bentuk (x,y), dengan x ialah nombor halaman, y ialah nombor indeks dalam tatasusunan.
  • null bitmap - Menandai lajur versi tertentu yang mengandungi nilai null (NULL). NULL bukan salah satu daripada nilai jenis data biasa, jadi atribut mesti disimpan secara berasingan.

Akibatnya, pengepala agak besar - sekurang-kurangnya 23 bait untuk setiap versi baris, dan biasanya lebih disebabkan oleh peta bit NULL. Jika jadual adalah "sempit" (iaitu, mengandungi beberapa lajur), overhed mungkin mengambil lebih daripada maklumat berguna.

memasukkan

Mari kita lihat dengan lebih dekat cara operasi rentetan peringkat rendah dilakukan, bermula dengan sisipan.

Untuk percubaan, mari buat jadual baharu dengan dua lajur dan indeks pada salah satu daripadanya:

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

Mari masukkan satu baris selepas memulakan transaksi.

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

Berikut ialah nombor transaksi semasa kami:

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

Mari lihat kandungan halaman tersebut. Fungsi heap_page_items bagi sambungan pageinspect membolehkan anda mendapatkan maklumat tentang penunjuk dan versi baris:

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

Ambil perhatian bahawa perkataan timbunan dalam PostgreSQL merujuk kepada jadual. Ini adalah satu lagi penggunaan istilah yang pelik - timbunan diketahui struktur data, yang tidak mempunyai persamaan dengan jadual. Di sini perkataan itu digunakan dalam erti kata "semuanya dilemparkan bersama-sama," berbanding dengan indeks tersusun.

Fungsi menunjukkan data "seadanya", dalam format yang sukar difahami. Untuk mengetahuinya, kami akan meninggalkan hanya sebahagian daripada maklumat dan menguraikannya:

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

Inilah yang kami lakukan:

  • Menambah sifar pada nombor indeks untuk menjadikannya kelihatan sama dengan t_ctid: (nombor halaman, nombor indeks).
  • Menghurai keadaan penuding lp_flags. Di sini ia adalah "biasa" - ini bermakna penunjuk sebenarnya merujuk kepada versi rentetan. Nanti kita tengok maksud lain.
  • Daripada semua bit maklumat, hanya dua pasangan telah dikenal pasti setakat ini. Bit xmin_committed dan xmin_aborted menunjukkan sama ada nombor transaksi xmin dilakukan (digugurkan). Dua bit serupa merujuk kepada nombor transaksi xmax.

Apa yang kita nampak? Apabila anda memasukkan baris, indeks nombor 1 akan muncul dalam halaman jadual, menunjuk ke versi pertama dan satu-satunya baris.

Dalam versi rentetan, medan xmin diisi dengan nombor transaksi semasa. Urus niaga masih aktif, jadi kedua-dua bit xmin_committed dan xmin_aborted tidak ditetapkan.

Medan ctid versi baris merujuk kepada baris yang sama. Ini bermakna bahawa versi yang lebih baru tidak wujud.

Medan xmax diisi dengan nombor tiruan 0 kerana versi baris ini belum dipadamkan dan semasa. Transaksi tidak akan memberi perhatian kepada nombor ini kerana bit xmax_aborted ditetapkan.

Mari kita ambil satu langkah lagi ke arah meningkatkan kebolehbacaan dengan menambahkan bit maklumat pada nombor transaksi. Dan mari kita buat fungsi, kerana kita memerlukan permintaan lebih daripada sekali:

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

Dalam borang ini, adalah lebih jelas perkara yang berlaku dalam pengepala versi baris:

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

Maklumat yang serupa, tetapi kurang terperinci, boleh diperoleh daripada jadual itu sendiri, menggunakan pseudo-lajur xmin dan xmax:

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

Penetapan

Jika transaksi berjaya diselesaikan, anda perlu mengingati statusnya - ambil perhatian bahawa ia telah dilakukan. Untuk melakukan ini, struktur yang dipanggil XACT digunakan (dan sebelum versi 10 ia dipanggil CLOG (komit log) dan nama ini masih boleh ditemui di tempat yang berbeza).

XACT bukan jadual katalog sistem; ini ialah fail dalam direktori PGDATA/pg_xact. Mereka mempunyai dua bit yang diperuntukkan untuk setiap transaksi: komited dan digugurkan - sama seperti dalam pengepala versi baris. Maklumat ini dibahagikan kepada beberapa fail semata-mata untuk kemudahan; kami akan kembali kepada isu ini apabila kami mempertimbangkan untuk membekukan. Dan kerja dengan fail ini dijalankan halaman demi halaman, seperti dengan semua yang lain.

Jadi, apabila transaksi dilakukan dalam XACT, bit komited ditetapkan untuk transaksi ini. Dan ini sahaja yang berlaku semasa melakukan (walaupun kita tidak bercakap tentang log pra-rakaman lagi).

Apabila transaksi lain mengakses halaman jadual yang baru kita lihat, ia perlu menjawab beberapa soalan.

  1. Adakah transaksi xmin selesai? Jika tidak, maka versi rentetan yang dibuat tidak sepatutnya kelihatan.
    Semakan ini dilakukan dengan melihat struktur lain, yang terletak dalam memori bersama contoh dan dipanggil ProcArray. Ia mengandungi senarai semua proses aktif, dan untuk setiap satu bilangan transaksi semasa (aktif) ditunjukkan.
  2. Jika selesai, bagaimana - dengan melakukan atau membatalkan? Jika dibatalkan, maka versi baris juga tidak boleh kelihatan.
    Inilah sebenarnya tujuan XACT. Tetapi, walaupun halaman terakhir XACT disimpan dalam penimbal dalam RAM, ia masih mahal untuk menyemak XACT setiap kali. Oleh itu, setelah status transaksi ditentukan, ia ditulis kepada bit xmin_committed dan xmin_aborted versi rentetan. Jika salah satu bit ini ditetapkan, maka keadaan transaksi xmin dianggap diketahui dan transaksi seterusnya tidak perlu mengakses XACT.

Mengapa bit ini tidak ditetapkan oleh transaksi itu sendiri yang melakukan sisipan? Apabila sisipan berlaku, transaksi belum tahu sama ada ia akan berjaya. Dan pada saat membuat komitmen, tidak lagi jelas baris mana di mana halaman diubah. Mungkin terdapat banyak halaman sedemikian, dan menghafalnya tidak menguntungkan. Di samping itu, beberapa halaman boleh diusir dari cache penimbal ke cakera; membacanya sekali lagi untuk menukar bit akan melambatkan komit dengan ketara.

Kelemahan penjimatan ialah selepas perubahan, sebarang transaksi (walaupun yang melakukan pembacaan mudah - SELECT) boleh mula menukar halaman data dalam cache penimbal.

Jadi, mari kita betulkan perubahan itu.

=> COMMIT;

Tiada apa-apa yang berubah pada halaman (tetapi kami tahu bahawa status transaksi telah direkodkan dalam XACT):

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

Kini transaksi yang mengakses halaman terlebih dahulu perlu menentukan status transaksi xmin dan menulisnya pada bit maklumat:

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

Pemadaman

Apabila baris dipadamkan, nombor transaksi pemadaman semasa ditulis pada medan xmax versi semasa dan bit xmax_aborted dikosongkan.

Ambil perhatian bahawa nilai set xmax yang sepadan dengan transaksi aktif bertindak sebagai kunci baris. Jika transaksi lain ingin mengemas kini atau memadam baris ini, ia akan terpaksa menunggu sehingga transaksi xmax selesai. Kami akan bercakap lebih lanjut mengenai menyekat kemudian. Buat masa ini, kami hanya ambil perhatian bahawa bilangan kunci baris adalah tidak terhad. Mereka tidak mengambil ruang dalam RAM dan prestasi sistem tidak mengalami bilangan mereka. Benar, urus niaga "lama" mempunyai kelemahan lain, tetapi lebih lanjut mengenainya kemudian.

Mari padamkan baris itu.

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

Kami melihat bahawa nombor transaksi ditulis dalam medan xmax, tetapi bit maklumat tidak ditetapkan:

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

pembatalan

Menggugurkan perubahan berfungsi sama seperti melakukan, hanya dalam XACT bit yang digugurkan ditetapkan untuk transaksi. Membatalkan adalah sepantas melakukan. Walaupun arahan itu dipanggil ROLLBACK, perubahan tidak digulung semula: segala-galanya yang berjaya diubah oleh transaksi dalam halaman data kekal tidak berubah.

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

Apabila halaman diakses, status akan disemak dan bit petunjuk xmax_aborted akan ditetapkan kepada versi baris. Nombor xmax itu sendiri kekal pada halaman, tetapi tiada siapa yang akan melihatnya.

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

Kemas kini

Kemas kini berfungsi seolah-olah ia mula-mula memadamkan versi semasa baris dan kemudian memasukkan yang baharu.

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

Pertanyaan menghasilkan satu baris (versi baharu):

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

Tetapi pada halaman kami melihat kedua-dua versi:

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

Versi yang dipadamkan ditandakan dengan nombor transaksi semasa dalam medan xmax. Selain itu, nilai ini ditulis di atas nilai lama, kerana transaksi sebelumnya telah dibatalkan. Dan bit xmax_aborted dikosongkan kerana status transaksi semasa belum diketahui.

Versi pertama baris kini merujuk kepada yang kedua (medan t_ctid) sebagai yang lebih baharu.

Indeks kedua muncul dalam halaman indeks dan baris kedua merujuk versi kedua dalam halaman jadual.

Sama seperti pemadaman, nilai xmax dalam versi pertama baris adalah petunjuk bahawa baris itu dikunci.

Baiklah, mari kita selesaikan transaksi.

=> COMMIT;

Indeks

Setakat ini kita hanya bercakap tentang halaman jadual. Apa yang berlaku di dalam indeks?

Maklumat dalam halaman indeks sangat berbeza bergantung pada jenis indeks tertentu. Dan walaupun satu jenis indeks mempunyai jenis halaman yang berbeza. Sebagai contoh, B-tree mempunyai halaman metadata dan halaman "biasa".

Walau bagaimanapun, halaman biasanya mempunyai tatasusunan penunjuk ke baris dan baris itu sendiri (sama seperti halaman jadual). Di samping itu, di hujung halaman terdapat ruang untuk data khas.

Baris dalam indeks juga boleh mempunyai struktur yang sangat berbeza bergantung pada jenis indeks. Contohnya, untuk pokok B, baris yang berkaitan dengan halaman daun mengandungi nilai kunci pengindeksan dan rujukan (ctid) kepada baris jadual yang sepadan. Secara umum, indeks boleh distrukturkan dengan cara yang sama sekali berbeza.

Perkara yang paling penting ialah tiada versi baris dalam indeks apa-apa jenis. Baiklah, atau kita boleh menganggap bahawa setiap baris diwakili oleh tepat satu versi. Dalam erti kata lain, tiada medan xmin dan xmax dalam pengepala baris indeks. Kita boleh menganggap bahawa pautan daripada indeks membawa kepada semua versi jadual baris - jadi anda boleh mengetahui versi mana yang akan dilihat oleh transaksi hanya dengan melihat jadual. (Seperti biasa, ini bukan keseluruhan kebenaran. Dalam sesetengah kes, peta keterlihatan boleh mengoptimumkan proses, tetapi kami akan melihat perkara ini dengan lebih terperinci kemudian.)

Pada masa yang sama, dalam halaman indeks kami mencari petunjuk kepada kedua-dua versi, kedua-dua versi semasa dan yang lama:

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

Urus niaga maya

Dalam amalan, PostgreSQL menggunakan pengoptimuman yang membolehkannya "menyimpan" nombor transaksi.

Jika transaksi hanya membaca data, ia tidak mempunyai kesan pada keterlihatan versi baris. Oleh itu, proses perkhidmatan terlebih dahulu mengeluarkan xid maya kepada transaksi. Nombor tersebut terdiri daripada ID proses dan nombor urutan.

Mengeluarkan nombor ini tidak memerlukan penyegerakan antara semua proses dan oleh itu sangat pantas. Kami akan berkenalan dengan sebab lain untuk menggunakan nombor maya apabila kita bercakap tentang pembekuan.

Nombor maya tidak diambil kira dalam apa jua cara dalam petikan data.

Pada masa yang berbeza, mungkin terdapat transaksi maya dalam sistem dengan nombor yang telah digunakan, dan ini adalah perkara biasa. Tetapi nombor sedemikian tidak boleh ditulis ke dalam halaman data, kerana pada kali berikutnya halaman itu diakses, ia mungkin kehilangan semua makna.

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

Jika urus niaga mula menukar data, ia diberi nombor transaksi yang sebenar dan unik.

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

=> COMMIT;

Transaksi Bersarang

Simpan mata

Ditakrifkan dalam SQL simpan mata (savepoint), yang membolehkan anda membatalkan sebahagian daripada transaksi tanpa mengganggunya sepenuhnya. Tetapi ini tidak sesuai dengan gambar rajah di atas, kerana urus niaga mempunyai status yang sama untuk semua perubahannya, dan secara fizikal tiada data digulung semula.

Untuk melaksanakan fungsi ini, urus niaga dengan titik simpanan dibahagikan kepada beberapa berasingan urus niaga bersarang (subtransaksi), yang statusnya boleh diuruskan secara berasingan.

Urus niaga bersarang mempunyai nombornya sendiri (lebih tinggi daripada bilangan transaksi utama). Status transaksi bersarang direkodkan dengan cara biasa dalam XACT, tetapi status akhir bergantung pada status transaksi utama: jika ia dibatalkan, maka semua transaksi bersarang juga dibatalkan.

Maklumat tentang sarang transaksi disimpan dalam fail dalam direktori PGDATA/pg_subtrans. Fail diakses melalui penimbal dalam memori kongsi contoh, disusun dengan cara yang sama seperti penimbal XACT.

Jangan kelirukan transaksi bersarang dengan transaksi autonomi. Urus niaga autonomi tidak bergantung antara satu sama lain dalam apa jua cara, tetapi urus niaga bersarang bergantung. Tiada urus niaga autonomi dalam PostgreSQL biasa, dan, mungkin, untuk yang terbaik: mereka diperlukan sangat, sangat jarang, dan kehadiran mereka dalam DBMS lain menimbulkan penyalahgunaan, yang mana semua orang kemudiannya menderita.

Mari kosongkan jadual, mulakan transaksi dan masukkan baris:

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

Sekarang mari letakkan titik simpan dan masukkan baris lain.

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

Ambil perhatian bahawa fungsi txid_current() mengembalikan nombor transaksi utama, bukan nombor transaksi bersarang.

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

Mari kita kembali ke titik simpan dan masukkan baris ketiga.

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

Dalam halaman kami terus melihat baris yang ditambahkan oleh transaksi bersarang yang dibatalkan.

Kami membetulkan perubahan.

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

Kini anda dapat melihat dengan jelas bahawa setiap transaksi bersarang mempunyai statusnya sendiri.

Ambil perhatian bahawa transaksi bersarang tidak boleh digunakan secara eksplisit dalam SQL, iaitu, anda tidak boleh memulakan transaksi baharu tanpa melengkapkan transaksi semasa. Mekanisme ini diaktifkan secara tersirat apabila menggunakan titik simpanan, serta semasa mengendalikan pengecualian PL/pgSQL dan dalam beberapa kes lain yang lebih eksotik.

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

Ralat dan keatoman operasi

Apakah yang berlaku jika ralat berlaku semasa menjalankan operasi? Sebagai contoh, seperti ini:

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

Ralat telah berlaku. Kini urus niaga dianggap dibatalkan dan tiada operasi dibenarkan di dalamnya:

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

Dan walaupun anda cuba melakukan perubahan, PostgreSQL akan melaporkan pengguguran:

=> COMMIT;
ROLLBACK

Mengapa transaksi tidak boleh diteruskan selepas kegagalan? Hakikatnya adalah bahawa ralat boleh timbul sedemikian rupa sehingga kita akan mendapat akses kepada sebahagian daripada perubahan - atomicity walaupun transaksi itu, tetapi pengendali akan dilanggar. Seperti dalam contoh kami, di mana pengendali berjaya mengemas kini satu baris sebelum ralat:

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

Ia mesti dikatakan bahawa psql mempunyai mod yang masih membenarkan transaksi diteruskan selepas kegagalan seolah-olah tindakan pengendali yang salah telah ditarik balik.

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

Tidak sukar untuk meneka bahawa dalam mod ini, psql sebenarnya meletakkan titik simpan tersirat sebelum setiap arahan, dan sekiranya berlaku kegagalan memulakan pengembalian kepadanya. Mod ini tidak digunakan secara lalai, kerana menetapkan titik simpanan (walaupun tanpa berbalik kepada mereka) melibatkan overhed yang ketara.

sambungan.

Sumber: www.habr.com

Tambah komen