SAVEPOINT

Rollback parsial dengan SAVEPOINT untuk transaksi bertahap.

Daftar isi

SAVEPOINT menandai sebuah titik di dalam transaksi berjalan sehingga pembatalan dapat berhenti pada titik itu alih-alih membatalkan seluruh transaksi. Topik ini membahas sintaks SAVEPOINT, ROLLBACK TO SAVEPOINT, dan RELEASE SAVEPOINT, aturan yang menyertai masa hidupnya, serta penerapannya pada peminjaman buku yang diproses bertahap. Setelah membacanya, transaksi panjang dapat dibagi menjadi tahap-tahap yang sebagian dapat dibatalkan tanpa mengorbankan tahap yang telah sah.

Menandai titik dan membatalkan ke titik#

Pernyataan SAVEPOINT nama mencatat titik simpan pada posisi pernyataan itu dijalankan, dan ROLLBACK TO SAVEPOINT nama membatalkan seluruh perubahan sejak titik tersebut tanpa mengakhiri transaksinya. Perubahan sebelum titik simpan tetap hidup dan transaksi tetap terbuka sampai COMMIT atau ROLLBACK penuh ditemui. Bukti berikut menyisipkan tiga anggota baru dengan satu titik simpan di antaranya.

Basis data latihan perpustakaan beserta tabel buku, anggota, dan peminjaman dibuat ulang oleh skema berikut.

sql skema-perpustakaan.sql
-- tidak-dijalankan
DROP DATABASE IF EXISTS perpustakaan;
CREATE DATABASE perpustakaan;
USE perpustakaan;

CREATE TABLE buku (
  id      INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  judul   VARCHAR(120) NOT NULL,
  penulis VARCHAR(80) NOT NULL,
  stok    TINYINT UNSIGNED NOT NULL DEFAULT 0
) ENGINE=InnoDB;

CREATE TABLE anggota (
  id   INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  nama VARCHAR(80) NOT NULL
) ENGINE=InnoDB;

CREATE TABLE peminjaman (
  id         INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  anggota_id INT UNSIGNED NOT NULL,
  buku_id    INT UNSIGNED NOT NULL,
  jumlah     TINYINT UNSIGNED NOT NULL DEFAULT 1,
  FOREIGN KEY (anggota_id) REFERENCES anggota (id),
  FOREIGN KEY (buku_id)    REFERENCES buku (id)
) ENGINE=InnoDB;

INSERT INTO buku (judul, penulis, stok) VALUES
  ('Dasar Basis Data Relasional', 'Dewi Sartika',  5),
  ('Struktur Data dan Algoritma', 'Ali Rahman',    3),
  ('Jaringan Komputer Dasar',     'Budi Hartono',  4),
  ('Pemrograman SQL Praktis',     'Citra Lestari', 6),
  ('Sistem Operasi Modern',       'Rina Kartika',  2);

INSERT INTO anggota (nama) VALUES ('Ali'), ('Budi'), ('Citra'), ('Dewi');
bash
# membuat ulang basis data perpustakaan beserta isinya
mysql -u root < skema-perpustakaan.sql
bash
# menyisipkan tiga anggota, dua terakhir dibatalkan ke titik simpan
mysql -t -u root perpustakaan <<'SQL'
START TRANSACTION;
INSERT INTO anggota (nama) VALUES ('Eka');
SAVEPOINT titik_awal;
INSERT INTO anggota (nama) VALUES ('Fajar');
INSERT INTO anggota (nama) VALUES ('Gita');
SELECT id, nama FROM anggota ORDER BY id;
ROLLBACK TO SAVEPOINT titik_awal;
SELECT id, nama FROM anggota ORDER BY id;
COMMIT;
SELECT id, nama FROM anggota ORDER BY id;
SQL
text Keluaran
+----+-------+
| id | nama  |
+----+-------+
|  1 | Ali   |
|  2 | Budi  |
|  3 | Citra |
|  4 | Dewi  |
|  5 | Eka   |
|  6 | Fajar |
|  7 | Gita  |
+----+-------+
+----+-------+
| id | nama  |
+----+-------+
|  1 | Ali   |
|  2 | Budi  |
|  3 | Citra |
|  4 | Dewi  |
|  5 | Eka   |
+----+-------+
+----+-------+
| id | nama  |
+----+-------+
|  1 | Ali   |
|  2 | Budi  |
|  3 | Citra |
|  4 | Dewi  |
|  5 | Eka   |
+----+-------+

Pembacaan pertama menunjukkan lima anggota bawaan ditambah tiga sisipan, pembacaan kedua setelah ROLLBACK TO SAVEPOINT menyisakan sisipan sebelum titik simpan saja, dan pembacaan ketiga setelah COMMIT mengonfirmasi keadaan yang ditetapkan. Baris Fajar dan Gita menghilang seluruhnya, sedangkan baris Eka dan empat anggota sebelumnya selamat karena berada sebelum titik simpan.

Masa hidup savepoint#

Savepoint dapat dilepas sebelum transaksi berakhir dengan RELEASE SAVEPOINT nama. Nama yang sama boleh dideklarasikan ulang, dan deklarasi terbaru menggantikan deklarasi lama. Seluruh savepoint hilang ketika transaksi berakhir, baik lewat COMMIT maupun ROLLBACK penuh, sehingga ROLLBACK TO SAVEPOINT sesudahnya gagal dengan error 1305. Perilaku lengkapnya diperlihatkan rangkaian berikut.

bash
# melepas savepoint lalu mendeklarasikan ulang nama yang sama
mysql -t -u root perpustakaan <<'SQL'
START TRANSACTION;
SAVEPOINT tahap;
INSERT INTO anggota (nama) VALUES ('Hana');
RELEASE SAVEPOINT tahap;
SAVEPOINT tahap;
INSERT INTO anggota (nama) VALUES ('Indra');
ROLLBACK TO SAVEPOINT tahap;
SELECT id, nama FROM anggota WHERE id > 4 ORDER BY id;
COMMIT;
SQL
text Keluaran
+----+------+
| id | nama |
+----+------+
|  5 | Eka  |
|  8 | Hana |
+----+------+

Baris Hana selamat karena RELEASE SAVEPOINT hanya melepas penanda, bukan membatalkan perubahan. Setelah penanda dinyatakan ulang, pembatalan hanya menyentuh sisipan Indra yang berada setelah deklarasi terbaru. Perhatikan pula penomorannya, yaitu Hana menerima id 8, bukan 6, karena penghitung AUTO_INCREMENT tidak turun kembali oleh pembatalan Fajar dan Gita pada percobaan sebelumnya.

Savepoint yang sudah ditetapkan tidak dapat dijangkau lagi setelah transaksinya berakhir dengan COMMIT, dan percobaan itu berujung error yang dapat direproduksi sebagai berikut.

bash
# savepoint telah hilang bersama transaksi yang sudah ditetapkan
mysql -u root perpustakaan <<'SQL' 2>&1
ROLLBACK TO SAVEPOINT titik_awal;
SQL
text Keluaran
ERROR 1305 (42000) at line 1: SAVEPOINT titik_awal does not exist

Peminjaman bertahap dengan dua savepoint#

Skenario nyata savepoint adalah transaksi yang memproses beberapa kegiatan sekaligus, misalnya satu anggota mengajukan peminjaman dua buku dan satu anggota lain mengajukan satu buku, lalu tahap akhir ternyata batal. Setiap tahap diberi savepoint, dan tahap yang batal dibatalkan ke penandanya tanpa menyentuh tahap sebelumnya.

bash
# tiga tahap peminjaman, tahap dua dan tiga dibatalkan
mysql -t -u root perpustakaan <<'SQL'
START TRANSACTION;
INSERT INTO peminjaman (anggota_id, buku_id, jumlah) VALUES (1, 1, 1);
UPDATE buku SET stok = stok - 1 WHERE id = 1;
SAVEPOINT tahap_2;
INSERT INTO peminjaman (anggota_id, buku_id, jumlah) VALUES (1, 2, 2);
UPDATE buku SET stok = stok - 2 WHERE id = 2;
SAVEPOINT tahap_3;
INSERT INTO peminjaman (anggota_id, buku_id, jumlah) VALUES (2, 4, 1);
UPDATE buku SET stok = stok - 1 WHERE id = 4;
ROLLBACK TO SAVEPOINT tahap_3;
ROLLBACK TO SAVEPOINT tahap_2;
COMMIT;
SELECT id, anggota_id, buku_id, jumlah FROM peminjaman ORDER BY id;
SELECT id, stok FROM buku WHERE id IN (1, 2, 4) ORDER BY id;
SQL
text Keluaran
+----+------------+---------+--------+
| id | anggota_id | buku_id | jumlah |
+----+------------+---------+--------+
|  1 |          1 |       1 |      1 |
+----+------------+---------+--------+
+----+------+
| id | stok |
+----+------+
|  1 |    4 |
|  2 |    3 |
|  4 |    6 |
+----+------+

Hanya peminjaman tahap pertama yang tersimpan, dan stok buku pertama berkurang menjadi empat, sedangkan stok buku kedua dan keempat kembali ke nilai awalnya karena kedua tahap itu dibatalkan. Inilah perbedaan pokok savepoint dengan transaksi tersarang yang tidak ada pada MySQL, yaitu pembatalan parsial di dalam satu kesatuan yang tetap ditetapkan bersama-sama saat COMMIT.

Pemulihan setelah error pernyataan#

Error pada sebuah pernyataan, misalnya pelanggaran foreign key, hanya menghentikan pernyataan itu dan tidak menggulung balik transaksinya. Transaksi tetap terbuka, dan seluruh pekerjaan sebelumnya masih berada di dalam transaksi itu. SAVEPOINT memberi dua pilihan pada saat itu, yaitu membatalkan tahap yang bermasalah lalu melanjutkan, atau membiarkan tahap itu dan menggulung balik seluruh transaksi di akhir. Contoh berikut menyertakan opsi --force pada klien agar eksekusi berlanjut sesudah error, sebagaimana aplikasi yang menangkap error lalu melanjutkan pekerjaannya.

bash
# tahap gagal karena foreign key, lalu dilanjutkan setelah pembatalan
rm -f pemulihan.out
mysql -t --force -u root perpustakaan <<'SQL' >pemulihan.out 2>&1
START TRANSACTION;
INSERT INTO peminjaman (anggota_id, buku_id, jumlah) VALUES (1, 2, 1);
SAVEPOINT tahap_anggota;
INSERT INTO peminjaman (anggota_id, buku_id, jumlah) VALUES (99, 2, 1);
ROLLBACK TO SAVEPOINT tahap_anggota;
INSERT INTO peminjaman (anggota_id, buku_id, jumlah) VALUES (2, 2, 1);
COMMIT;
SELECT id, anggota_id, buku_id, jumlah FROM peminjaman ORDER BY id;
SQL
cat pemulihan.out
rm -f pemulihan.out
text Keluaran
ERROR 1452 (23000) at line 4: Cannot add or update a child row: a foreign key constraint fails (`perpustakaan`.`peminjaman`, CONSTRAINT `peminjaman_ibfk_1` FOREIGN KEY (`anggota_id`) REFERENCES `anggota` (`id`))
+----+------------+---------+--------+
| id | anggota_id | buku_id | jumlah |
+----+------------+---------+--------+
|  1 |          1 |       1 |      1 |
|  4 |          1 |       2 |      1 |
|  6 |          2 |       2 |      1 |
+----+------------+---------+--------+

Sisipan untuk anggota bernomor 99 gagal dengan error 1452 karena anggota itu tidak ada, tetapi transaksi tetap hidup dan dua sisipan yang sah tersimpan setelah COMMIT. Penomorannya melompat dari 1 ke 4 dan 6 karena setiap percobaan penyisipan, termasuk yang gagal, tetap menghabiskan nomor AUTO_INCREMENT. Pola ini menegaskan bahwa error pernyataan bukan akhir transaksi, dan keputusan batal atau lanjut sepenuhnya ada di tangan penulisnya.

Ringkasan#

  • SAVEPOINT menandai titik di dalam transaksi, dan ROLLBACK TO SAVEPOINT membatalkan perubahan sejak titik itu tanpa mengakhiri transaksi.
  • RELEASE SAVEPOINT hanya melepas penanda dan tidak mengembalikan data, sedangkan deklarasi ulang nama yang sama menggantikan penanda lama.
  • Seluruh savepoint hilang saat COMMIT atau ROLLBACK penuh, sehingga pembatalan ke titik yang sudah lewat menghasilkan error 1305.
  • Error pada satu pernyataan tidak menggulung balik transaksi, sehingga ROLLBACK TO SAVEPOINT menjadi jalur pemulihan untuk melanjutkan pekerjaan yang sah.
  • Penghitung AUTO_INCREMENT tidak turun kembali karena pembatalan parsial, sehingga penomoran baris dapat melompat.

Referensi#