Halaman ini memperkenalkan berbagai tingkat isolasi dan menjelaskan cara kerjanya di Spanner.
Tingkat isolasi adalah properti database yang menentukan data mana yang terlihat oleh transaksi serentak. Spanner mendukung dua tingkat isolasi yang ditentukan dalam standar SQL ANSI/ISO: serializable dan repeatable read. Saat membuat transaksi, Anda harus memilih tingkat isolasi yang paling sesuai untuk transaksi tersebut. Tingkat isolasi yang dipilih memungkinkan setiap transaksi memprioritaskan berbagai faktor seperti latensi, tingkat pembatalan, dan apakah aplikasi rentan terhadap efek anomali data. Pilihan terbaik bergantung pada permintaan spesifik workload.
Isolasi serialisabel
Isolasi serialisabel adalah tingkat isolasi default di Spanner. Dengan isolasi yang dapat diserialisasi, Spanner memberi Anda jaminan kontrol konkurensi yang paling ketat untuk transaksi, yang disebut konsistensi eksternal. Spanner berperilaku seolah-olah semua transaksi dieksekusi secara berurutan, meskipun Spanner sebenarnya menjalankannya di beberapa server (dan mungkin di beberapa pusat data) untuk performa dan ketersediaan yang lebih tinggi daripada database server tunggal. Selain itu, jika satu transaksi selesai sebelum transaksi lain mulai di-commit, Spanner menjamin bahwa klien selalu melihat hasil transaksi dalam urutan berurutan. Secara intuitif, Spanner mirip dengan database satu mesin.
Sebagai gantinya, Spanner dapat membatalkan transaksi jika workload memiliki pertentangan baca-tulis yang tinggi, di mana banyak transaksi membaca data yang sedang diupdate oleh transaksi lain, karena sifat mendasar dari transaksi yang dapat diserialisasi. Namun, ini adalah default yang baik untuk database operasional. Hal ini membantu Anda menghindari masalah pengaturan waktu yang rumit yang biasanya hanya muncul dengan konkurensi tinggi. Masalah ini sulit direproduksi dan dipecahkan masalahnya. Oleh karena itu, isolasi serialisabel memberikan perlindungan terkuat terhadap anomali data. Jika transaksi perlu dicoba lagi, mungkin ada peningkatan latensi karena percobaan ulang transaksi.
Isolasi repeatable read
Di Spanner, isolasi baca yang dapat diulang diimplementasikan menggunakan teknik yang umumnya dikenal sebagai snapshot isolation. Isolasi repeatable read di Spanner memastikan bahwa semua operasi baca dalam transaksi melihat snapshot database yang konsisten, atau kuat, seperti yang ada di awal transaksi. Hal ini juga menjamin bahwa penulisan serentak pada data yang sama hanya berhasil jika tidak ada konflik. Pendekatan ini bermanfaat dalam skenario konflik baca-tulis tinggi saat banyak transaksi membaca data yang mungkin dimodifikasi oleh transaksi lain. Dengan menggunakan snapshot tetap, pembacaan yang dapat diulang menghindari dampak performa dari tingkat isolasi serializable yang lebih ketat. Operasi baca dapat dieksekusi tanpa memperoleh kunci dan tanpa memblokir operasi tulis serentak, sehingga menghasilkan lebih sedikit transaksi yang dibatalkan yang mungkin perlu dicoba lagi karena potensi konflik serialisasi. Dalam kasus penggunaan saat klien Anda sudah menjalankan semuanya dalam transaksi baca-tulis, dan sulit untuk mendesain ulang dan menggunakan transaksi hanya baca, Anda dapat menggunakan isolasi baca berulang untuk meningkatkan latensi beban kerja Anda.
Tidak seperti isolasi yang dapat diserialisasi, bacaan yang dapat diulang dapat menyebabkan anomali data jika aplikasi Anda mengandalkan hubungan atau batasan data tertentu yang tidak diterapkan oleh skema database, terutama saat urutan operasi penting. Dalam kasus seperti itu, transaksi dapat membaca data, membuat keputusan berdasarkan data tersebut, lalu menulis perubahan yang melanggar batasan khusus aplikasi tersebut, meskipun batasan skema database masih terpenuhi. Hal ini terjadi karena isolasi baca yang dapat diulang memungkinkan transaksi serentak berlanjut tanpa serialisasi yang ketat. Satu potensi anomali dikenal sebagai penyimpangan penulisan, yang muncul dari jenis pembaruan serentak tertentu, di mana setiap pembaruan diterima secara independen, tetapi efek gabungannya melanggar integritas data aplikasi. Misalnya, bayangkan ada sistem rumah sakit yang mewajibkan setidaknya satu dokter untuk selalu siap sedia, dan dokter dapat meminta untuk tidak bertugas selama satu shift. Dengan isolasi baca yang dapat diulang, jika Dr. Richards dan Dr. Smith dijadwalkan untuk bertugas pada shift yang sama dan secara bersamaan mencoba meminta untuk tidak bertugas, setiap permintaan akan berhasil secara paralel. Hal ini karena kedua transaksi membaca bahwa setidaknya ada satu dokter lain yang dijadwalkan untuk bertugas jaga di awal transaksi, sehingga menyebabkan anomali data jika transaksi berhasil. Di sisi lain, penggunaan isolasi yang dapat diserialisasi mencegah transaksi ini melanggar batasan karena transaksi yang dapat diserialisasi akan mendeteksi potensi anomali data dan membatalkan transaksi. Dengan demikian, konsistensi aplikasi dipastikan dengan menerima rasio pembatalan yang lebih tinggi.
Dalam contoh sebelumnya, Anda dapat
menggunakan klausa SELECT FOR UPDATE
dalam isolasi baca yang dapat diulang.
Klausul SELECT…FOR UPDATE
memverifikasi apakah data yang dibacanya pada snapshot yang dipilih tetap tidak berubah pada waktu commit. Demikian pula, pernyataan DML
dan mutasi, yang membaca data secara internal untuk
memastikan integritas penulisan, juga memverifikasi bahwa data tetap tidak berubah
pada waktu commit.
Untuk mengetahui informasi selengkapnya, lihat Menggunakan isolasi baca yang dapat diulang.
Contoh kasus penggunaan
Contoh berikut menunjukkan manfaat penggunaan isolasi baca yang dapat diulang untuk menghilangkan overhead penguncian. Transaction 1
dan
Transaction 2
berjalan dalam isolasi baca yang dapat diulang.
Transaction 1
menetapkan stempel waktu snapshot saat pernyataan SELECT
dijalankan.
GoogleSQL
-- Transaction 1
BEGIN;
-- Snapshot established at T1
SELECT AlbumId, MarketingBudget
FROM Albums
WHERE SingerId = 1;
/*-----------+------------------*
| AlbumId | MarketingBudget |
+------------+------------------+
| 1 | 50000 |
| 2 | 100000 |
| 3 | 70000 |
| 4 | 80000 |
*------------+------------------*/
PostgreSQL
-- Transaction 1
BEGIN;
-- Snapshot established at T1
SELECT albumid, marketingbudget
FROM albums
WHERE singerid = 1;
/*-----------+------------------*
| albumid | marketingbudget |
+------------+------------------+
| 1 | 50000 |
| 2 | 100000 |
| 3 | 70000 |
| 4 | 80000 |
*------------+------------------*/
Kemudian, Transaction 2
menetapkan stempel waktu snapshot setelah Transaction 1
dimulai, tetapi sebelum di-commit. Karena Transaction 1
belum memperbarui data, kueri
SELECT
di Transaction 2
membaca data yang sama dengan Transaction 1
.
GoogleSQL
-- Transaction 2
BEGIN;
-- Snapshot established at T2 > T1
SELECT AlbumId, MarketingBudget
FROM Albums
WHERE SingerId = 1;
INSERT INTO Albums (SingerId, AlbumId, MarketingBudget) VALUES (1, 5, 50000);
COMMIT;
PostgreSQL
-- Transaction 2
BEGIN;
-- Snapshot established at T2 > T1
SELECT albumid, marketingbudget
FROM albums
WHERE singerid = 1;
INSERT INTO albums (singerid, albumid, marketingbudget) VALUES (1, 5, 50000);
COMMIT;
Transaction 1
berlanjut setelah Transaction 2
dilakukan.
GoogleSQL
-- Transaction 1 continues
SELECT SUM(MarketingBudget) as UsedBudget
FROM Albums
WHERE SingerId = 1;
/*-----------*
| UsedBudget |
+------------+
| 300000 |
*------------*/
PostgreSQL
-- Transaction 1 continues
SELECT SUM(marketingbudget) AS usedbudget
FROM albums
WHERE singerid = 1;
/*-----------*
| usedbudget |
+------------+
| 300000 |
*------------*/
Nilai UsedBudget
yang ditampilkan Spanner adalah jumlah
anggaran yang dibaca oleh Transaction 1
. Jumlah ini hanya mencerminkan data yang ada di snapshot T1
. Hal ini tidak mencakup anggaran yang ditambahkan Transaction 2
, karena Transaction 2
melakukan commit setelah Transaction 1
membuat snapshot T1
. Menggunakan repeatable read berarti Transaction 1
tidak harus dibatalkan meskipun Transaction 2
mengubah data yang dibaca oleh Transaction 1
. Namun,
hasil yang ditampilkan Spanner mungkin atau mungkin tidak sesuai dengan
yang diinginkan.
Konflik baca-tulis dan kebenaran
Dalam contoh sebelumnya, jika data yang dikueri oleh pernyataan SELECT
di
Transaction 1
digunakan untuk membuat keputusan anggaran pemasaran berikutnya, mungkin ada masalah kebenaran.
Misalnya, anggaplah ada total anggaran sebesar 400,000
. Berdasarkan hasil
dari pernyataan SELECT
di Transaction 1
, kita mungkin berpikir bahwa ada
100,000
yang tersisa dalam anggaran dan memutuskan untuk mengalokasikannya semua ke AlbumId = 4
.
GoogleSQL
-- Transaction 1 continues..
UPDATE Albums
SET MarketingBudget = MarketingBudget + 100000
WHERE SingerId = 1 AND AlbumId = 4;
COMMIT;
PostgreSQL
-- Transaction 1 continues..
UPDATE albums
SET marketingbudget = marketingbudget + 100000
WHERE singerid = 1 AND albumid = 4;
COMMIT;
Transaction 1
berhasil melakukan commit, meskipun Transaction 2
telah mengalokasikan 50,000
dari anggaran 100,000
yang tersisa ke album baru AlbumId = 5
.
Anda dapat menggunakan sintaksis SELECT...FOR UPDATE
untuk memvalidasi bahwa pembacaan transaksi tertentu tidak berubah selama masa aktif transaksi untuk menjamin kebenaran transaksi. Pada contoh berikut yang menggunakan
SELECT...FOR UPDATE
, Transaction 1
dibatalkan pada waktu commit.
GoogleSQL
-- Transaction 1 continues..
SELECT SUM(MarketingBudget) AS TotalBudget
FROM Albums
WHERE SingerId = 1
FOR UPDATE;
/*-----------*
| TotalBudget |
+------------+
| 300000 |
*------------*/
COMMIT;
PostgreSQL
-- Transaction 1 continues..
SELECT SUM(marketingbudget) AS totalbudget
FROM albums
WHERE singerid = 1
FOR UPDATE;
/*-------------*
| totalbudget |
+-------------+
| 300000 |
*-------------*/
COMMIT;
Untuk mengetahui informasi selengkapnya, lihat Menggunakan SELECT FOR UPDATE dalam isolasi baca yang dapat diulang.
Konflik tulis-tulis dan kebenaran
Dengan menggunakan tingkat isolasi baca yang dapat diulang, penulisan serentak pada data yang sama hanya berhasil jika tidak ada konflik.
Dalam contoh berikut, Transaction 1
menetapkan stempel waktu snapshot pada
pernyataan SELECT
pertama.
GoogleSQL
-- Transaction 1
BEGIN;
-- Snapshot established at T1
SELECT AlbumId, MarketingBudget
FROM Albums
WHERE SingerId = 1;
PostgreSQL
-- Transaction 1
BEGIN;
-- Snapshot established at T1
SELECT albumid, marketingbudget
FROM albums
WHERE singerid = 1;
Transaction 2
berikut membaca data yang sama dengan Transaction 1
dan menyisipkan
item baru. Transaction 2
berhasil melakukan tanpa menunggu atau membatalkan.
GoogleSQL
-- Transaction 2
BEGIN;
-- Snapshot established at T2 (> T1)
SELECT AlbumId, MarketingBudget
FROM Albums
WHERE SingerId = 1;
INSERT INTO Albums (SingerId, AlbumId, MarketingBudget) VALUES (1, 5, 50000);
COMMIT;
PostgreSQL
-- Transaction 2
BEGIN;
-- Snapshot established at T2 (> T1)
SELECT albumid, marketingbudget
FROM albums
WHERE singerid = 1;
INSERT INTO albums (singerid, albumid, marketingbudget) VALUES (1, 5, 50000);
COMMIT;
Transaction 1
berlanjut setelah Transaction 2
dilakukan.
GoogleSQL
-- Transaction 1 continues
INSERT INTO Albums (SingerId, AlbumId, MarketingBudget) VALUES (1, 5, 30000);
-- Transaction aborts
COMMIT;
PostgreSQL
-- Transaction 1 continues
INSERT INTO albums (singerid, albumid, marketingbudget) VALUES (1, 5, 30000);
-- Transaction aborts
COMMIT;
Transaction 1
dibatalkan karena Transaction 2
telah melakukan penyisipan ke baris AlbumId = 5
.
Langkah berikutnya
Pelajari cara Menggunakan tingkat isolasi baca yang dapat diulang.
Pelajari cara Menggunakan SELECT FOR UPDATE dalam isolasi baca yang dapat diulang.
Pelajari cara Menggunakan SELECT FOR UPDATE dalam isolasi yang dapat diserialisasi.
Untuk mempelajari lebih lanjut serialisabilitas dan konsistensi eksternal Spanner, lihat TrueTime dan konsistensi eksternal.