Yah, kita sudah membahas
isolasi dan membuat penyimpangan tentang
struktur data tingkat rendah . Dan kami akhirnya mencapai hal yang paling menarik, yaitu, versi baris (tupel).
Header Tuple
Seperti yang telah disebutkan, beberapa versi dari setiap baris dapat secara bersamaan tersedia di database. Dan kita perlu entah bagaimana membedakan satu versi dari yang lain. Untuk tujuan ini, setiap versi diberi label dengan "waktu" (
xmin
) dan "waktu" (
xmax
) yang efektif. Tanda kutip menunjukkan bahwa penghitung kenaikan khusus digunakan daripada waktu itu sendiri. Dan penghitung ini adalah
pengidentifikasi transaksi .
(Seperti biasa, pada kenyataannya ini lebih rumit: ID transaksi tidak selalu dapat meningkat karena kedalaman bit counter yang terbatas. Tetapi kami akan mengeksplorasi lebih detail tentang ini ketika diskusi kami mencapai titik beku.)
Ketika sebuah baris dibuat, nilai
xmin
ditetapkan sama dengan ID transaksi yang melakukan perintah INSERT, sementara
xmax
tidak diisi.
Ketika satu baris dihapus, nilai
xmax
dari versi saat ini diberi label dengan ID transaksi yang dilakukan HAPUS.
Perintah UPDATE sebenarnya melakukan dua operasi berikutnya: HAPUS dan INSERT. Di versi baris saat ini,
xmax
diset sama dengan ID transaksi yang melakukan UPDATE. Kemudian versi baru dari baris yang sama dibuat, di mana nilai
xmin
sama dengan
xmax
dari versi sebelumnya.
Bidang
xmin
dan
xmax
termasuk dalam header versi baris. Selain bidang-bidang ini, tupel header berisi yang lain, seperti:
infomask
- beberapa bit yang menentukan sifat-sifat tuple yang diberikan. Ada beberapa dari mereka, dan kami akan membahasnya setiap waktu.ctid
- referensi ke versi baris berikutnya yang sama, lebih baru. ctid
dari referensi versi baris terbaru, terbaru, dan sangat versi itu. Angka tersebut dalam bentuk (x,y)
, di mana x
adalah nomor halaman dan y
adalah nomor urut pointer dalam array.- Bitmap NULLs, yang menandai kolom-kolom dari versi tertentu yang berisi NULL. NULL bukan nilai reguler tipe data, dan oleh karena itu, kami harus menyimpan karakteristik ini secara terpisah.
Hasilnya, tajuknya terlihat cukup besar: minimal 23 byte per setiap tuple, tetapi biasanya lebih besar karena bitmap NULLs. Jika tabel βsempitβ (artinya, berisi beberapa kolom), byte overhead dapat menempati lebih banyak ruang daripada informasi yang bermanfaat.
Masukkan
Mari kita lihat lebih detail bagaimana operasi pada baris dilakukan pada level rendah, dan kita mulai dengan insert.
Untuk bereksperimen, kami akan membuat tabel baru dengan dua kolom dan indeks pada salah satunya:
=> CREATE TABLE t( id serial, s text ); => CREATE INDEX ON t(s);
Kami memulai transaksi untuk memasukkan baris.
=> BEGIN; => INSERT INTO t(s) VALUES ('FOO');
Ini adalah ID transaksi kami saat ini:
=> SELECT txid_current();
txid_current -------------- 3664 (1 row)
Mari kita melihat isi halaman. Fungsi
heap_page_items
dari ekstensi "pageinspect" memungkinkan kami untuk mendapatkan 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" di PostgreSQL menunjukkan tabel. Ini adalah satu lagi penggunaan istilah yang aneh: heap adalah
struktur data yang diketahui, yang tidak ada hubungannya dengan tabel. Kata ini digunakan di sini dalam arti bahwa "semuanya menumpuk", tidak seperti dalam indeks yang diurutkan.
Fungsi ini menunjukkan data "apa adanya", dalam format yang sulit dipahami. Untuk mengklarifikasi hal-hal tersebut, kami hanya menyisakan sebagian informasi dan menafsirkannya:
=> 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)
Kami melakukan yang berikut:
- Menambahkan nol ke nomor penunjuk agar terlihat seperti
t_ctid
: (nomor halaman, nomor penunjuk). - Menafsirkan status pointer
lp_flags
. Ini "normal" di sini, yang berarti bahwa penunjuk sebenarnya mereferensikan versi baris. Kami akan membahas nilai-nilai lain nanti. - Dari semua bit informasi, kami memilih hanya dua pasangan sejauh ini. bit
xmin_aborted
dan xmin_aborted
menunjukkan apakah transaksi dengan ID xmin
dilakukan (dibatalkan). Sepasang bit serupa berkaitan dengan transaksi dengan ID xmax
.
Apa yang kita amati? Ketika sebuah baris dimasukkan, pada halaman tabel akan muncul sebuah pointer yang memiliki nomor 1 dan merujuk pada versi pertama dan satu-satunya dari baris tersebut.
Bidang
xmin
dalam tupel diisi dengan ID transaksi saat ini. Karena transaksi masih aktif, bit
xmin_aborted
dan
xmin_aborted
tidak disetel.
Bidang
ctid
dari versi baris merujuk ke baris yang sama. Itu berarti tidak ada versi yang lebih baru tersedia.
Kolom
xmax
diisi dengan angka konvensional 0 karena tupel tidak dihapus, yaitu mutakhir. Transaksi akan mengabaikan nomor ini karena set bit
xmax_aborted
.
Mari kita bergerak satu langkah lagi untuk meningkatkan keterbacaan dengan menambahkan bit informasi ke ID transaksi. Dan mari kita buat fungsinya karena kita akan membutuhkan kueri 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;
Apa yang terjadi di header versi baris itu jauh lebih jelas dalam formulir ini:
=> SELECT * FROM heap_page('t',0);
ctid | state | xmin | xmax | t_ctid -------+--------+------+-------+-------- (0,1) | normal | 3664 | 0 (a) | (0,1) (1 row)
Kita bisa mendapatkan informasi yang serupa, tetapi jauh lebih tidak detail, dari tabel itu sendiri dengan menggunakan
xmin
dan
xmax
pseudo-kolom:
=> SELECT xmin, xmax, * FROM t;
xmin | xmax | id | s ------+------+----+----- 3664 | 0 | 1 | FOO (1 row)
Berkomitmen
Ketika transaksi berhasil, statusnya harus diingat, yaitu, transaksi harus ditandai sebagai komitmen. Untuk tujuan ini, struktur XACT digunakan. (Sebelum versi 10 itu disebut CLOG (commit log), dan Anda masih cenderung menemukan nama ini.)
XACT bukan tabel katalog sistem, tetapi file dalam direktori PGDATA / pg_xact. Dua bit dialokasikan dalam file-file ini untuk setiap transaksi - "berkomitmen" dan "dibatalkan" - persis dengan cara yang sama seperti pada tuple header. Informasi ini tersebar di beberapa file hanya untuk kenyamanan; kita akan kembali ke ini ketika kita membahas pembekuan. PostgreSQL berfungsi dengan file-file ini halaman demi halaman, seperti yang lainnya.
Jadi, ketika transaksi dilakukan, bit "berkomitmen" diatur untuk transaksi ini di XACT. Dan inilah yang terjadi ketika transaksi dilakukan (meskipun kami belum menyebutkan log write-ahead).
Ketika beberapa transaksi lain mengakses halaman tabel yang baru saja kita lihat, yang pertama harus menjawab beberapa pertanyaan.
- Apakah transaksi
xmin
selesai? Jika tidak, tupel yang dibuat tidak boleh terlihat.
Ini diperiksa dengan melihat melalui struktur lain, yang terletak di memori bersama contoh dan disebut ProcArray. Struktur ini menyimpan daftar semua proses aktif, bersama dengan ID transaksi saat ini (aktif) untuk masing-masing. - Jika transaksi selesai, apakah itu dilakukan atau dibatalkan? Jika diputar kembali, tuple juga tidak boleh terlihat.
Inilah yang dibutuhkan XACT. Tetapi mahal untuk memeriksa XACT setiap kali, meskipun halaman terakhir XACT disimpan dalam buffer dalam memori bersama. Oleh karena itu, setelah menemukan, status transaksi ditulis ke xmin_committed
dan xmin_aborted
bit dari tuple. Jika salah satu dari bit-bit ini ditetapkan, status transaksi diperlakukan sebagai diketahui dan transaksi berikutnya tidak perlu memeriksa XACT.
Mengapa transaksi yang melakukan penyisipan tidak mengatur bit-bit ini? Ketika penyisipan dilakukan, transaksi belum mengetahui apakah itu akan berhasil diselesaikan. Dan pada saat komit, sudah tidak jelas baris mana dan di mana halaman diubah. Mungkin ada banyak halaman seperti itu, dan tidak praktis untuk melacaknya. Selain itu, beberapa halaman dapat diusir ke disk dari cache buffer; membacanya lagi untuk mengubah bit berarti perlambatan signifikan dari komit.
Sisi kebalikan dari penghematan biaya adalah bahwa setelah pembaruan, transaksi apa pun (bahkan yang melakukan SELECT) dapat mulai mengubah halaman data dalam cache buffer.
Jadi, kami melakukan perubahan.
=> COMMIT;
Tidak ada yang berubah di halaman (tapi kami tahu bahwa status transaksi sudah ditulis ke 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 akan 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, ID dari transaksi penghapusan saat ini ditulis ke bidang
xmax_aborted
dari versi terbaru dan bit
xmax_aborted
-reset.
Perhatikan bahwa nilai
xmax
terkait dengan transaksi aktif berfungsi sebagai kunci baris. Jika transaksi lain akan memperbarui atau menghapus baris ini, itu harus menunggu sampai transaksi
xmax
selesai. Kita akan membicarakan tentang kunci lebih detail nanti. Pada titik ini, hanya perhatikan bahwa jumlah kunci baris tidak terbatas sama sekali. Mereka tidak menempati memori, dan kinerja sistem tidak terpengaruh oleh angka itu. Namun, transaksi jangka panjang memiliki kelemahan lain, yang juga akan dibahas nanti.
Mari kita hapus satu baris.
=> BEGIN; => DELETE FROM t; => SELECT txid_current();
txid_current -------------- 3665 (1 row)
Kami melihat bahwa ID transaksi ditulis ke 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
Batalkan transaksi berfungsi sama dengan komit, kecuali bahwa bit "dibatalkan" diatur dalam XACT. Batalkan dilakukan secepat komit. Meskipun perintah itu disebut ROLLBACK, perubahan tidak dibatalkan: segala sesuatu yang sudah diubah oleh transaksi, tetap tidak tersentuh.
=> 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, status akan diperiksa dan bit petunjuk
xmax_aborted
akan ditetapkan. Meskipun nomor
xmax
itu sendiri akan tetap di halaman, itu tidak akan dilihat.
=> 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 versi saat ini dihapus pertama dan kemudian yang baru dimasukkan.
=> BEGIN; => UPDATE t SET s = 'BAR'; => SELECT txid_current();
txid_current -------------- 3666 (1 row)
Kueri mengembalikan satu baris (versi baru):
=> SELECT * FROM t;
id | s ----+----- 1 | BAR (1 row)
Tetapi kita dapat melihat kedua versi di halaman:
=> 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 dihapus ditandai dengan ID transaksi saat ini di bidang
xmax
. Selain itu, nilai ini telah menimpa yang lama sejak transaksi sebelumnya dibatalkan. Dan bit
xmax_aborted
-reset karena status transaksi saat ini belum diketahui.
Versi pertama dari baris sekarang merujuk yang kedua, sebagai yang lebih baru.
Halaman indeks sekarang berisi pointer kedua dan baris kedua, yang mereferensikan versi kedua di halaman tabel.
Cara yang sama seperti untuk menghapus, nilai
xmax
di versi pertama menunjukkan bahwa baris dikunci.
Terakhir, kami melakukan transaksi.
=> COMMIT;
Indeks
Kami hanya berbicara tentang halaman tabel sejauh ini. Tetapi apa yang terjadi di dalam indeks?
Informasi dalam halaman indeks sangat tergantung pada jenis indeks spesifik. Selain itu, bahkan satu jenis indeks dapat memiliki berbagai jenis halaman. Sebagai contoh: B-tree memiliki halaman metadata dan halaman "normal".
Namun demikian, halaman indeks biasanya memiliki array pointer ke baris dan baris sendiri (seperti halaman tabel). Selain itu, beberapa ruang di akhir halaman dialokasikan untuk data khusus.
Baris dalam indeks juga dapat memiliki struktur yang berbeda tergantung pada tipe indeks. Sebagai contoh: di pohon-B, baris yang berkaitan dengan halaman daun berisi nilai kunci pengindeksan dan referensi (
ctid
) ke baris tabel yang sesuai. Secara umum, indeks dapat disusun dengan cara yang sangat berbeda.
Poin utamanya adalah bahwa dalam indeks jenis apa pun tidak ada
versi baris. Atau kita dapat menganggap setiap baris hanya diwakili oleh satu versi. Dengan kata lain, tajuk baris indeks tidak berisi bidang
xmin
dan
xmax
. Untuk saat ini kita dapat mengasumsikan bahwa referensi dari titik indeks ke semua versi baris tabel. Jadi untuk mengetahui versi baris mana yang terlihat oleh suatu transaksi, PostgreSQL perlu melihat ke dalam tabel. (Seperti biasa, ini bukan keseluruhan cerita. Terkadang peta visibilitas memungkinkan untuk mengoptimalkan proses, tetapi kita akan membahas ini nanti.)
Di sini, di halaman indeks, kami menemukan petunjuk untuk kedua versi: yang terbaru dan yang sebelumnya:
=> 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 mengambil keuntungan dari optimasi yang memungkinkan untuk "hemat" mengeluarkan ID transaksi.
Jika suatu transaksi hanya membaca data, itu sama sekali tidak mempengaruhi visibilitas tuple. Oleh karena itu, pertama proses backend memberikan ID virtual (virtual xid) untuk transaksi. ID ini terdiri dari pengidentifikasi proses dan nomor urut.
Penugasan ID virtual ini tidak memerlukan sinkronisasi antara semua proses dan karenanya dilakukan dengan sangat cepat. Kita akan mempelajari alasan lain menggunakan ID virtual ketika kita membahas pembekuan.
Snapshots data tidak mempertimbangkan ID virtual akun sama sekali.
Pada titik waktu yang berbeda, sistem dapat melakukan transaksi virtual dengan ID yang sudah digunakan, dan ini tidak masalah. Tetapi ID ini tidak dapat ditulis ke halaman data karena ketika halaman diakses lain kali, ID tersebut dapat menjadi tidak berarti.
=> BEGIN; => SELECT txid_current_if_assigned();
txid_current_if_assigned -------------------------- (1 row)
Tetapi jika suatu transaksi mulai mengubah data, ia menerima ID transaksi yang benar dan unik.
=> UPDATE accounts SET amount = amount - 1.00; => SELECT txid_current_if_assigned();
txid_current_if_assigned -------------------------- 3667 (1 row)
=> COMMIT;
Subtransaksi
Savepoints
Dalam SQL,
savepoints didefinisikan, yang memungkinkan memutar kembali beberapa operasi transaksi tanpa aborsi yang lengkap. Tetapi ini tidak sesuai dengan model di atas karena status transaksi adalah satu untuk semua perubahan dan tidak ada data yang secara fisik dibatalkan.
Untuk mengimplementasikan fungsi ini, transaksi dengan savepoint dibagi menjadi beberapa
subtransaksi terpisah yang statusnya dapat dikelola secara terpisah.
Subtrabaksi memiliki ID sendiri (lebih besar dari ID transaksi utama). Status subtransaksi ditulis ke XACT dengan cara yang biasa, tetapi status akhir tergantung pada status transaksi utama: jika digulirkan kembali, semua transaksi juga digulung kembali.
Informasi tentang subtransaksi bersarang disimpan dalam file-file dari direktori PGDATA / pg_subtrans. File-file ini diakses melalui buffer dalam memori bersama instance, yang disusun dengan cara yang sama seperti buffer XACT.
Jangan bingung subtransaksi dengan transaksi otonom. Transaksi otonom sama sekali tidak bergantung satu sama lain, sementara subtransaksi memang bergantung. Tidak ada transaksi mandiri dalam PostgreSQL biasa, yang, mungkin, menjadi lebih baik: mereka sebenarnya sangat jarang dibutuhkan, dan ketersediaannya dalam DBMS lain mengundang penyalahgunaan, yang diderita semua orang.
Mari kita membersihkan tabel, memulai transaksi dan memasukkan 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 kita membuat savepoint dan memasukkan baris lain.
=> SAVEPOINT sp; => INSERT INTO t(s) VALUES ('XYZ'); => SELECT txid_current();
txid_current -------------- 3669 (1 row)
Perhatikan bahwa fungsi
txid_current
mengembalikan ID transaksi utama daripada subtransaksi.
=> 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 kembali ke savepoint dan masukkan baris ketiga.
=> ROLLBACK TO sp; => INSERT INTO t 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 subtransaksi gulung belakang.
Melakukan 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)
Jelas terlihat sekarang bahwa setiap subtransaksi memiliki statusnya sendiri.
Perhatikan bahwa SQL tidak mengizinkan penggunaan subtransaksi secara eksplisit, artinya, Anda tidak dapat memulai transaksi baru sebelum menyelesaikan transaksi saat ini. Teknik ini terlibat secara implisit ketika savepoint digunakan dan juga ketika menangani pengecualian PL / pgSQL, serta dalam beberapa situasi 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 kekompakan operasi
Apa yang terjadi jika kesalahan terjadi saat operasi sedang dilakukan? 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
Terjadi kesalahan. Sekarang transaksi diperlakukan sebagai dibatalkan dan tidak ada operasi yang diizinkan di dalamnya:
=> SELECT * FROM t;
ERROR: current transaction is aborted, commands ignored until end of transaction block
Dan bahkan jika kita mencoba melakukan perubahan, PostgreSQL akan melaporkan rollback:
=> COMMIT;
ROLLBACK
Mengapa tidak mungkin melanjutkan eksekusi transaksi setelah gagal? Masalahnya adalah bahwa kesalahan dapat terjadi sehingga kita akan mendapatkan akses ke bagian dari perubahan, yaitu, atomicity akan rusak tidak hanya untuk transaksi, tetapi bahkan untuk satu operator. Misalnya, dalam contoh kami, operator dapat memperbarui satu baris sebelum kesalahan terjadi:
=> 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)
Perlu dicatat bahwa psql memiliki mode yang memungkinkan melanjutkan transaksi setelah kegagalan, seolah-olah efek dari 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 mengetahui bahwa dalam mode ini, psql benar-benar menetapkan savepoint implisit sebelum setiap perintah dan memulai rollback untuk itu jika terjadi kegagalan. Mode ini tidak digunakan secara default karena membangun savepoint (bahkan tanpa rollback) memerlukan overhead yang signifikan.
Baca terus .