Jadi, kami melihat masalah yang berkaitan dengan
isolasi dan membuat penyimpangan tentang
pengorganisasian data pada tingkat rendah . Dan akhirnya sampai ke yang paling menarik - ke versi garis.
Berita utama
Seperti yang telah kami katakan, setiap baris dapat secara bersamaan hadir dalam database dalam beberapa versi. Satu versi harus dibedakan dari yang lain, untuk tujuan ini, setiap versi memiliki dua tanda yang menentukan "waktu" dari tindakan versi ini (xmin dan xmax). Dalam tanda kutip - karena bukan waktu yang digunakan seperti itu, tetapi penghitung kenaikan khusus. Dan penghitung ini adalah nomor transaksi.
(Seperti biasa, ini sebenarnya lebih rumit: jumlah transaksi tidak dapat bertambah setiap saat karena kapasitas bit counter yang terbatas. Tetapi kami akan mempertimbangkan detail ini secara terperinci ketika kami sampai di pembekuan.)
Ketika garis dibuat, xmin diatur ke jumlah transaksi yang dieksekusi perintah INSERT, dan xmax tidak terisi.
Ketika satu baris dihapus, nilai xmax dari versi saat ini ditandai dengan nomor transaksi yang dilakukan HAPUS.
Ketika sebuah baris dimodifikasi dengan perintah UPDATE, dua operasi sebenarnya dilakukan: HAPUS dan INSERT. Dalam versi baris saat ini, xmax diset sama dengan jumlah transaksi yang melakukan UPDATE. Kemudian versi baru dari baris yang sama dibuat; nilainya xmin cocok dengan nilai xmax dari versi sebelumnya.
Bidang xmin dan xmax termasuk dalam header versi baris. Selain bidang ini, tajuk berisi yang lain, misalnya:
- infomask - serangkaian bit yang mendefinisikan properti versi ini. Ada cukup banyak dari mereka; yang utama akan kita pertimbangkan secara bertahap.
- ctid - tautan ke versi berikutnya yang lebih baru dari baris yang sama. Dalam versi terbaru, string terbaru, ctid merujuk ke versi ini sendiri. Angka memiliki bentuk (x, y), di mana x adalah nomor halaman, y adalah nomor seri dari penunjuk dalam array.
- bitmap dari nilai yang tidak ditentukan - menandai kolom-kolom dari versi ini yang berisi nilai yang tidak ditentukan (NULL). NULL bukan salah satu nilai tipe data yang biasa, jadi atribut harus disimpan secara terpisah.
Akibatnya, tajuknya cukup besar - setidaknya 23 byte per versi string, dan biasanya lebih karena bitmap NULL. Jika tabelnya "sempit" (artinya, memuat beberapa kolom), overhead mungkin memerlukan lebih dari informasi yang berguna.
Masukkan
Mari kita lihat lebih dekat bagaimana operasi string dilakukan pada level rendah, dan mulai dengan insert.
Untuk percobaan, buat tabel baru dengan dua kolom dan indeks pada salah satunya:
=> CREATE TABLE t( id serial, s text ); => CREATE INDEX ON t(s);
Masukkan satu baris, setelah memulai transaksi.
=> BEGIN; => INSERT INTO t(s) VALUES ('FOO');
Inilah jumlah transaksi kami saat ini:
=> SELECT txid_current();
txid_current -------------- 3664 (1 row)
Lihatlah isi halaman. Fungsi heap_page_items dari ekstensi pageinspect menyediakan informasi tentang pointer 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
Perhatikan bahwa kata heap (heap) di PostgreSQL mengacu pada tabel. Ini adalah penggunaan aneh dari istilah - heap adalah
struktur data terkenal yang tidak ada hubungannya dengan tabel. Di sini, kata ini digunakan dalam arti "semuanya bertumpuk," berbeda dengan indeks yang dipesan.
Fungsi ini menampilkan data "apa adanya" dalam format yang sulit dibaca. Untuk memahami, kami hanya akan meninggalkan sebagian informasi dan mendekripsi:
=> 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:
- Kami menambahkan nol ke nomor indeks untuk membawanya ke bentuk yang sama dengan t_ctid: (nomor halaman, nomor indeks).
- Mendekripsi status pointer lp_flags. Ini dia "normal" - ini berarti bahwa pointer benar-benar merujuk pada versi string. Nilai-nilai lain akan dipertimbangkan nanti.
- Dari semua bit informasi, sejauh ini hanya dua pasangan telah dialokasikan. Bit xmin_committed dan xmin_aborted menunjukkan apakah transaksi dengan nomor xmin dilakukan (dibatalkan). Dua bit serupa merujuk ke nomor transaksi xmax.
Apa yang kita lihat Saat Anda menyisipkan baris di halaman tabel, sebuah pointer muncul dengan nomor 1, merujuk pada versi pertama dan satu-satunya dari baris.
Dalam versi baris, bidang xmin diisi dengan jumlah transaksi saat ini. Transaksi masih aktif, sehingga kedua bit xmin_committed dan xmin_aborted tidak disetel.
Bidang ctid dari versi baris merujuk ke baris yang sama. Ini berarti bahwa versi yang lebih baru tidak ada.
Bidang xmax diisi dengan angka dummy 0, karena versi baris ini tidak dihapus dan relevan. Transaksi tidak akan memperhatikan nomor ini, karena bit xmax_aborted diatur.
Mari kita mengambil satu langkah lagi untuk meningkatkan keterbacaan dengan menambahkan bit informasi ke nomor transaksi. Dan kami akan membuat fungsi, karena kami akan membutuhkan permintaan lebih dari 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 bentuk ini, jauh lebih jelas apa yang terjadi di header versi string:
=> SELECT * FROM heap_page('t',0);
ctid | state | xmin | xmax | t_ctid -------+--------+------+-------+-------- (0,1) | normal | 3664 | 0 (a) | (0,1) (1 row)
Serupa, tetapi secara substansial kurang terperinci, informasi dapat diperoleh dari tabel itu sendiri, menggunakan pseudo-kolom xmin dan xmax:
=> SELECT xmin, xmax, * FROM t;
xmin | xmax | id | s ------+------+----+----- 3664 | 0 | 1 | FOO (1 row)
Fiksasi
Setelah berhasil menyelesaikan transaksi, Anda perlu mengingat statusnya - perhatikan bahwa itu sudah diperbaiki. Untuk melakukan ini, gunakan struktur yang disebut XACT (dan sebelum versi 10 itu disebut CLOG (commit log) dan nama ini masih dapat ditemukan di tempat yang berbeda).
XACT bukan tabel katalog sistem; Ini adalah file dalam direktori PGDATA / pg_xact. Di dalamnya, untuk setiap transaksi, dua bit dialokasikan: dikomit dan dibatalkan - persis sama seperti di header versi baris. Informasi ini dibagi menjadi beberapa file semata-mata untuk kenyamanan, kami akan kembali ke masalah ini ketika kami mempertimbangkan pembekuan. Dan bekerja dengan file-file ini dilakukan halaman demi halaman, seperti yang lainnya.
Jadi, ketika melakukan transaksi dalam XACT, bit yang berkomitmen ditetapkan untuk transaksi ini. Dan hanya itu yang terjadi selama komit (meskipun kita belum berbicara tentang jurnal prarekam belum).
Ketika transaksi lain mengakses halaman tabel yang baru saja kita lihat, dia harus menjawab beberapa pertanyaan.
- Sudahkah transaksi xmin selesai? Jika tidak, versi string yang dihasilkan tidak akan terlihat.
Pemeriksaan ini dilakukan dengan melihat struktur lain yang terletak di memori bersama instance dan disebut ProcArray. Ini berisi daftar semua proses aktif, dan untuk setiap jumlah transaksi saat ini (aktif) ditunjukkan. - Jika selesai, lalu bagaimana - dengan fiksasi atau pembatalan? Jika dibatalkan, maka versi string juga tidak akan terlihat.
Inilah tepatnya tujuan XACT. Tetapi, meskipun halaman XACT terakhir disimpan dalam buffer dalam RAM, tidak perlu memeriksa XACT setiap kali. Oleh karena itu, status transaksi yang pernah diklarifikasi dicatat dalam bit xmin_committed dan xmin_aborted dari versi baris. Jika salah satu dari bit ini ditetapkan, maka status transaksi xmin dianggap diketahui dan transaksi berikutnya tidak lagi harus mengakses XACT.
Mengapa bit-bit ini tidak disetel oleh transaksi itu sendiri yang melakukan penyisipan? Ketika memasukkan terjadi, transaksi belum tahu apakah itu akan selesai dengan sukses. Dan pada saat memperbaikinya sudah tidak jelas baris mana halaman diubah. Mungkin ada banyak halaman seperti itu, dan menghafalnya tidak menguntungkan. Selain itu, sebagian halaman dapat didorong keluar dari cache buffer ke disk; membacanya lagi untuk mengubah bit berarti secara signifikan memperlambat komit.
Kelemahan dari penghematan adalah bahwa setelah perubahan, transaksi apa pun (bahkan melakukan pembacaan sederhana - SELECT) dapat mulai mengubah halaman data dalam cache buffer.
Jadi, perbaiki perubahan.
=> COMMIT;
Tidak ada yang berubah di halaman (tapi kami tahu bahwa status transaksi sudah dicatat dalam XACT):
=> SELECT * FROM heap_page('t',0);
ctid | state | xmin | xmax | t_ctid -------+--------+------+-------+-------- (0,1) | normal | 3664 | 0 (a) | (0,1) (1 row)
Sekarang transaksi yang pertama kali mengakses halaman harus menentukan status transaksi xmin dan menuliskannya ke bit informasi:
=> 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)
Hapus
Ketika sebuah baris dihapus, jumlah transaksi penghapusan saat ini dicatat dalam bidang xmax dari versi saat ini, dan bit xmax_aborted di-reset.
Perhatikan bahwa nilai xmax set yang terkait dengan transaksi aktif bertindak sebagai kunci baris. Jika transaksi lain akan memperbarui atau menghapus baris ini, itu akan dipaksa untuk menunggu transaksi xmax selesai. Kami akan berbicara lebih banyak tentang kunci nanti. Untuk saat ini, kami hanya mencatat bahwa jumlah kunci baris tidak terbatas. Mereka tidak menempati tempat dalam RAM dan kinerja sistem tidak menderita dari kuantitasnya. Benar, transaksi "panjang" memiliki kelemahan lain, tetapi lebih banyak tentang itu nanti.
Hapus garis.
=> BEGIN; => DELETE FROM t; => SELECT txid_current();
txid_current -------------- 3665 (1 row)
Kami melihat bahwa nomor transaksi dicatat di bidang xmax, tetapi bit informasi tidak disetel:
=> SELECT * FROM heap_page('t',0);
ctid | state | xmin | xmax | t_ctid -------+--------+----------+------+-------- (0,1) | normal | 3664 (c) | 3665 | (0,1) (1 row)
Batalkan
Mengembalikan perubahan berfungsi mirip dengan komit, hanya dalam XACT untuk transaksi bit yang dibatalkan dibatalkan. Pembatalan secepat komitmen. Meskipun perintah itu disebut ROLLBACK, perubahan tidak dibatalkan: segala sesuatu yang berhasil diubah oleh transaksi di halaman data tetap 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)
Saat mengakses halaman, statusnya akan diperiksa dan bit petunjuk xmax_aborted akan disetel dalam versi baris. Nomor xmax itu sendiri tetap ada di halaman, tetapi tidak ada 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)
Perbarui
Pembaruan berfungsi seolah-olah pertama kali menghapus versi baris saat ini, dan kemudian menyisipkan yang baru.
=> BEGIN; => UPDATE t SET s = 'BAR'; => SELECT txid_current();
txid_current -------------- 3666 (1 row)
Permintaan menghasilkan satu baris (versi baru):
=> SELECT * FROM t;
id | s ----+----- 1 | BAR (1 row)
Tetapi di halaman kita melihat kedua 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 jarak jauh ditandai dengan nomor transaksi saat ini di bidang xmax. Selain itu, nilai ini ditulis di atas yang lama, karena transaksi sebelumnya dibatalkan. Dan bit xmax_aborted di-reset, karena status transaksi saat ini masih belum diketahui.
Versi pertama dari baris sekarang merujuk ke yang kedua (bidang t_ctid), sebagai yang lebih baru.
Pointer kedua dan baris kedua muncul di halaman indeks, menghubungkan ke versi kedua di halaman tabel.
Seperti halnya penghapusan, nilai xmax dalam versi pertama string adalah tanda bahwa string dikunci.
Nah, selesaikan transaksi.
=> COMMIT;
Indeks
Sejauh ini, kami hanya berbicara tentang halaman tabular. Dan apa yang terjadi di dalam indeks?
Informasi dalam halaman indeks sangat tergantung pada jenis indeks tertentu. Dan bahkan satu jenis indeks memiliki berbagai jenis halaman. Sebagai contoh, B-tree memiliki halaman dengan halaman metadata dan โregulerโ.
Namun, halaman biasanya memiliki array pointer ke garis dan garis itu sendiri (seperti di halaman tabel). Selain itu, di akhir halaman ada tempat untuk data khusus.
Baris dalam indeks juga dapat memiliki struktur yang sangat berbeda tergantung pada jenis indeks. Misalnya, untuk B-tree, baris yang terkait dengan halaman daun berisi nilai kunci indeks dan tautan (ctid) ke baris tabel yang sesuai. Secara umum, indeks dapat diatur dengan cara yang sama sekali berbeda.
Poin yang paling penting adalah tidak ada versi baris dalam semua jenis indeks. Baik, atau kita dapat mengasumsikan bahwa setiap baris diwakili oleh tepat satu versi. Dengan kata lain, tidak ada bidang xmin dan xmax di header baris indeks. Kami dapat mengasumsikan bahwa tautan dari indeks mengarah ke semua versi tabular dari baris - jadi Anda hanya bisa mengetahui versi transaksi yang akan dilihat jika Anda melihat tabel. (Seperti biasa, ini bukan kebenaran keseluruhan. Dalam beberapa kasus, peta visibilitas memungkinkan Anda untuk mengoptimalkan proses, tetapi kami akan mempertimbangkan ini secara lebih rinci nanti.)
Pada saat yang sama, di halaman indeks kami menemukan pointer ke kedua versi, baik saat ini dan yang lama:
=> SELECT itemoffset, ctid FROM bt_page_items('t_s_idx',1);
itemoffset | ctid ------------+------- 1 | (0,2) 2 | (0,1) (2 rows)
Transaksi virtual
Dalam praktiknya, PostgreSQL menggunakan optimisasi untuk "menyimpan" nomor transaksi.
Jika suatu transaksi hanya membaca data, maka itu tidak mempengaruhi visibilitas versi baris. Oleh karena itu, pada awalnya, proses penyajian mengeluarkan transaksi nomor virtual (virtual xid). Angka terdiri dari pengidentifikasi proses dan nomor urut.
Penerbitan nomor ini tidak memerlukan sinkronisasi antara semua proses dan karenanya sangat cepat. Kita akan mengetahui alasan lain untuk menggunakan nomor virtual ketika kita berbicara tentang pembekuan.
Nomor virtual tidak diperhitungkan dalam snapshot data.
Pada titik waktu yang berbeda, transaksi virtual dengan angka yang telah digunakan mungkin muncul di sistem, dan ini normal. Tetapi angka seperti itu tidak dapat ditulis ke halaman data, karena saat Anda mengakses halaman berikutnya, itu mungkin kehilangan semua makna.
=> BEGIN; => SELECT txid_current_if_assigned();
txid_current_if_assigned -------------------------- (1 row)
Jika transaksi mulai mengubah data, itu diberikan nomor transaksi nyata 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 poin
SQL mendefinisikan savepoint yang memungkinkan Anda untuk membatalkan bagian dari suatu transaksi tanpa mengganggu sepenuhnya. Tetapi ini tidak cocok dengan skema di atas, karena status transaksi adalah satu untuk semua perubahannya, dan secara fisik tidak ada data yang digulung kembali.
Untuk mengimplementasikan fungsi tersebut, transaksi dengan titik penyimpanan dibagi menjadi beberapa
transaksi bersarang terpisah (subtransaksi), yang statusnya dapat dikontrol secara terpisah.
Transaksi bersarang memiliki nomor sendiri (lebih tinggi dari nomor transaksi utama). Status transaksi bersarang dicatat dengan cara yang biasa di XACT, namun, status final tergantung pada status transaksi utama: jika dibatalkan, maka semua transaksi yang bersarang juga dibatalkan.
Informasi tentang transaksi bersarang disimpan dalam file di direktori PGDATA / pg_subtrans. File diakses melalui buffer dalam memori bersama dari instance, diatur dengan cara yang sama seperti buffer XACT.
Jangan bingung antara transaksi bertingkat dan transaksi otonom. Transaksi otonom sama sekali tidak bergantung satu sama lain, dan yang bersarang tergantung. Tidak ada transaksi otonom di PostgreSQL biasa, dan, mungkin, untuk yang lebih baik: dalam kasus mereka sangat diperlukan, sangat jarang, dan kehadiran mereka dalam DBMSs lainnya memprovokasi pelecehan, dari mana semua orang kemudian menderita.
Kosongkan tabel, mulai 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 letakkan savepoint dan masukkan baris lain.
=> SAVEPOINT sp; => INSERT INTO t(s) VALUES ('XYZ'); => SELECT txid_current();
txid_current -------------- 3669 (1 row)
Perhatikan bahwa fungsi txid_current () mengembalikan jumlah transaksi utama, bukan 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)
Kami memutar kembali ke titik penyimpanan dan menyisipkan 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)
Di halaman, kami terus melihat baris yang ditambahkan oleh transaksi bersarang yang dibatalkan.
Kami memperbaiki 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)
Sekarang Anda dapat dengan jelas melihat bahwa setiap transaksi bersarang memiliki statusnya sendiri.
Perhatikan bahwa transaksi bersarang tidak dapat digunakan secara eksplisit dalam SQL, yaitu, Anda tidak dapat memulai transaksi baru tanpa menyelesaikan yang saat ini. Mekanisme ini digunakan secara implisit ketika menggunakan savepoints, dan juga ketika menangani pengecualian PL / pgSQL dan dalam sejumlah kasus lain yang lebih eksotis.
=> BEGIN;
BEGIN
=> BEGIN;
WARNING: there is already a transaction in progress BEGIN
=> COMMIT;
COMMIT
=> COMMIT;
WARNING: there is no transaction in progress COMMIT
Kesalahan dan atomitas operasi
Apa yang terjadi jika kesalahan terjadi selama operasi? Misalnya, 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
Telah terjadi kesalahan. Sekarang transaksi dianggap dibatalkan dan tidak satu operasi pun diizinkan di dalamnya:
=> SELECT * FROM t;
ERROR: current transaction is aborted, commands ignored until end of transaction block
Dan bahkan jika Anda mencoba melakukan perubahan, PostgreSQL akan melaporkan pembatalan:
=> COMMIT;
ROLLBACK
Mengapa saya tidak dapat melanjutkan transaksi setelah gagal? Faktanya adalah bahwa kesalahan dapat terjadi sehingga kita akan mendapatkan akses ke bagian dari perubahan - atomitas bahkan bukan transaksi, tetapi operator akan dilanggar. Seperti dalam contoh kami, di mana operator berhasil memperbarui satu baris sebelum kesalahan:
=> 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)
Saya harus mengatakan bahwa dalam psql ada mode yang masih memungkinkan Anda untuk melanjutkan transaksi setelah kegagalan, seolah-olah tindakan operator yang salah dibatalkan.
=> \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;
Sangat mudah untuk menebak bahwa dalam mode ini psql benar-benar menetapkan save point implisit di depan setiap perintah, dan dalam kasus kegagalan itu memulai rollback untuk itu. Mode ini tidak digunakan secara default, karena pengaturan titik penyimpanan (bahkan tanpa mengembalikannya) dikaitkan dengan overhead signifikan.
Untuk dilanjutkan.