Halaman ini menjelaskan transaksi di Spanner dan menyertakan contoh kode untuk menjalankan transaksi.
Pengantar
Transaksi di Spanner adalah kumpulan operasi baca dan tulis yang dijalankan secara acak pada satu titik waktu logis di seluruh kolom, baris, dan tabel dalam database.
Spanner mendukung mode transaksi berikut:
Mengunci baca-tulis. Transaksi ini mengandalkan penguncian pesimis dan, jika diperlukan, commit dua fase. Mengunci transaksi baca-tulis dapat dibatalkan, sehingga aplikasi harus mencoba lagi.
Hanya baca. Jenis transaksi ini memberikan konsistensi yang terjamin di seluruh beberapa operasi baca, tetapi tidak mengizinkan operasi tulis. Secara default, transaksi hanya baca dijalankan pada stempel waktu yang dipilih sistem yang menjamin konsistensi eksternal, tetapi transaksi tersebut juga dapat dikonfigurasi untuk membaca pada stempel waktu sebelumnya. Transaksi hanya baca tidak perlu di-commit dan tidak mengambil kunci. Selain itu, transaksi hanya baca mungkin menunggu penulisan yang sedang berlangsung selesai sebelum dieksekusi.
DML yang dipartisi. Jenis transaksi ini mengeksekusi pernyataan Bahasa Pengolahan Data (DML) sebagai DML Terpartisi. DML yang dipartisi dirancang untuk update dan penghapusan massal, terutama pembersihan dan pengisian ulang berkala. Jika Anda perlu melakukan commit pada sejumlah besar tulisan buta, tetapi tidak memerlukan transaksi atomik, Anda dapat mengubah tabel Spanner secara massal menggunakan penulisan batch. Untuk mengetahui informasi selengkapnya, lihat Mengubah data menggunakan operasi tulis batch.
Halaman ini menjelaskan properti umum dan semantik transaksi di Spanner serta memperkenalkan antarmuka transaksi DML Terpartisi, operasi baca-tulis, dan hanya-baca di Spanner.
Transaksi baca-tulis
Berikut adalah skenario saat Anda harus menggunakan transaksi baca-tulis penguncian:
- Jika Anda melakukan operasi tulis yang bergantung pada hasil satu atau
beberapa operasi baca, Anda harus melakukan operasi tulis dan baca tersebut dalam
transaksi baca-tulis yang sama.
- Contoh: melipatgandakan saldo rekening bank A. Pembacaan saldo A harus berada dalam transaksi yang sama dengan penulisan untuk mengganti saldo dengan nilai yang digandakan.
- Jika Anda melakukan satu atau beberapa operasi tulis yang perlu di-commit secara atomik, Anda
harus melakukan operasi tulis tersebut dalam transaksi baca-tulis yang sama.
- Contoh: transfer $200 dari akun A ke akun B. Kedua operasi tulis (satu untuk mengurangi A sebesar $200 dan satu untuk menaikkan B sebesar $200) dan operasi baca saldo akun awal harus berada dalam transaksi yang sama.
- Jika Anda mungkin melakukan satu atau beberapa operasi tulis, bergantung pada hasil
satu atau beberapa operasi baca, Anda harus melakukan operasi tulis dan baca tersebut dalam
transaksi baca-tulis yang sama, meskipun operasi tulis tidak akhirnya dieksekusi.
- Contoh: transfer $200 dari rekening bank A ke rekening bank B jika saldo A saat ini lebih besar dari $500. Transaksi Anda harus berisi pembacaan saldo A dan pernyataan kondisional yang berisi operasi tulis.
Berikut adalah skenario saat Anda tidak boleh menggunakan transaksi baca-tulis penguncian:
- Jika hanya melakukan operasi baca, dan Anda dapat mengekspresikan operasi baca menggunakan metode baca tunggal, Anda harus menggunakan metode baca tunggal tersebut atau transaksi hanya baca. Operasi baca tunggal tidak mengunci, tidak seperti transaksi baca-tulis.
Properti
Transaksi baca-tulis di Spanner mengeksekusi serangkaian operasi baca dan tulis secara atomik pada satu titik waktu logis. Selain itu, stempel waktu saat transaksi baca-tulis dijalankan cocok dengan waktu jam dinding, dan urutan serialisasi cocok dengan urutan stempel waktu.
Mengapa menggunakan transaksi baca-tulis? Transaksi baca-tulis menyediakan properti ACID database relasional (Bahkan, transaksi baca-tulis Spanner menawarkan jaminan yang lebih kuat daripada ACID tradisional; lihat bagian Semantik di bawah).
Isolasi
Berikut adalah properti isolasi untuk transaksi baca-tulis dan hanya baca.
Transaksi yang membaca dan menulis
Berikut adalah properti isolasi yang Anda dapatkan setelah berhasil melakukan transaksi yang berisi serangkaian operasi baca (atau kueri) dan tulis:
- Semua operasi baca dalam transaksi menampilkan nilai yang mencerminkan snapshot konsisten yang diambil pada stempel waktu commit transaksi.
- Baris atau rentang kosong tetap kosong pada waktu commit.
- Semua operasi tulis dalam transaksi di-commit pada stempel waktu commit transaksi.
- Operasi tulis tidak terlihat oleh transaksi apa pun hingga setelah transaksi di-commit.
Driver klien Spanner tertentu berisi logika percobaan ulang transaksi untuk menyamarkan error sementara, yang dilakukan dengan menjalankan kembali transaksi dan memvalidasi data yang diamati klien.
Efeknya adalah semua operasi baca dan tulis tampaknya terjadi pada satu titik waktu, baik dari perspektif transaksi itu sendiri maupun dari perspektif pembaca dan penulis lain ke database Spanner. Dengan kata lain, pembacaan dan penulisan akhirnya terjadi pada stempel waktu yang sama (lihat ilustrasi ini di bagian Serialisasi dan konsistensi eksternal di bawah).
Transaksi yang hanya membaca
Jaminan untuk transaksi baca-tulis yang hanya membaca serupa: semua pembacaan dalam transaksi tersebut menampilkan data dari stempel waktu yang sama, bahkan untuk baris yang tidak ada. Salah satu perbedaannya adalah jika Anda membaca data, lalu melakukan commit transaksi baca-tulis tanpa penulisan apa pun, tidak ada jaminan bahwa data tidak berubah di database setelah pembacaan dan sebelum commit. Jika Anda ingin mengetahui apakah data telah berubah sejak Anda terakhir kali membacanya, pendekatan terbaiknya adalah membacanya lagi (baik dalam transaksi baca-tulis, atau menggunakan pembacaan yang kuat.) Selain itu, untuk efisiensi, jika Anda tahu sebelumnya bahwa Anda hanya akan membaca dan tidak menulis, Anda harus menggunakan transaksi hanya baca, bukan transaksi baca-tulis.
Atomitas, Konsistensi, Durabilitas
Selain properti Isolasi, Spanner menyediakan Atomicity (jika ada penulisan dalam commit transaksi, semuanya akan di-commit), Consistency (database tetap dalam status konsisten setelah transaksi), dan Durability (data yang di-commit tetap di-commit.)
Manfaat properti ini
Karena properti ini, sebagai developer aplikasi, Anda dapat berfokus pada kebenaran setiap transaksi itu sendiri, tanpa perlu khawatir tentang cara melindungi eksekusinya dari transaksi lain yang mungkin dieksekusi pada waktu yang sama.
Antarmuka
Library klien Spanner menyediakan antarmuka untuk menjalankan isi pekerjaan dalam konteks transaksi baca-tulis, dengan percobaan ulang untuk pembatalan transaksi. Berikut adalah sedikit konteks untuk menjelaskan poin ini: transaksi Spanner mungkin harus dicoba beberapa kali sebelum di-commit. Misalnya, jika dua transaksi mencoba menangani data secara bersamaan dengan cara yang mungkin menyebabkan deadlock, Spanner akan membatalkan salah satunya sehingga transaksi lainnya dapat membuat progres. (Lebih jarang, peristiwa sementara dalam Spanner dapat mengakibatkan beberapa transaksi dibatalkan.) Karena transaksi bersifat atomik, transaksi yang dibatalkan tidak akan memiliki efek yang terlihat pada database. Oleh karena itu, transaksi harus dieksekusi dengan mencobanya lagi hingga berhasil.
Saat menggunakan transaksi di library klien Spanner, Anda menentukan isi transaksi (yaitu, operasi baca dan tulis yang akan dilakukan pada satu atau beberapa tabel dalam database) dalam bentuk objek fungsi. Di balik layar, library klien Spanner menjalankan fungsi berulang kali hingga transaksi di-commit atau error yang tidak dapat dicoba ulang terjadi.
Contoh
Misalkan Anda menambahkan kolom MarketingBudget
ke
tabel Albums
yang ditampilkan di
halaman Skema dan Model Data:
CREATE TABLE Albums ( SingerId INT64 NOT NULL, AlbumId INT64 NOT NULL, AlbumTitle STRING(MAX), MarketingBudget INT64 ) PRIMARY KEY (SingerId, AlbumId);
Departemen pemasaran Anda memutuskan untuk melakukan upaya pemasaran untuk album yang diberi kunci oleh
Albums (1, 1)
dan telah meminta Anda untuk memindahkan $200.000 dari anggaran Albums
(2, 2)
, tetapi hanya jika uang tersebut tersedia dalam anggaran album tersebut. Anda harus
menggunakan transaksi baca-tulis penguncian untuk operasi ini, karena transaksi
mungkin melakukan operasi tulis bergantung pada hasil operasi baca.
Berikut ini cara menjalankan transaksi baca-tulis:
C++
C#
Go
Java
Node.js
PHP
Python
Ruby
Semantik
Serialisabilitas dan konsistensi eksternal
Spanner menyediakan 'serialisasi', yang berarti semua transaksi muncul seolah-olah dijalankan dalam urutan serial, meskipun beberapa operasi baca, tulis, dan operasi lain dari transaksi yang berbeda sebenarnya terjadi secara paralel. Spanner menetapkan stempel waktu commit yang mencerminkan urutan transaksi yang di-commit untuk menerapkan properti ini. Faktanya, Spanner menawarkan jaminan yang lebih kuat daripada serialisasi yang disebut konsistensi eksternal: transaksi melakukan commit dalam urutan yang tercermin dalam stempel waktu commit-nya, dan stempel waktu commit ini mencerminkan waktu nyata sehingga Anda dapat membandingkannya dengan smartwatch. Operasi baca dalam transaksi melihat semua yang telah di-commit sebelum transaksi di-commit, dan operasi tulis dilihat oleh semua yang dimulai setelah transaksi di-commit.
Misalnya, pertimbangkan eksekusi dua transaksi seperti yang diilustrasikan dalam diagram di bawah:
Transaksi Txn1
berwarna biru membaca beberapa data A
, buffering operasi tulis ke A
, lalu berhasil melakukan commit. Transaksi Txn2
berwarna hijau dimulai setelah Txn1
, membaca
beberapa data B
, lalu membaca data A
. Karena Txn2
membaca nilai A
setelah Txn1
melakukan operasi tulis ke A
, Txn2
akan melihat efek operasi tulis
Txn1
ke A
, meskipun Txn2
dimulai sebelum Txn1
selesai.
Meskipun ada beberapa tumpang-tindih waktu saat Txn1
dan Txn2
dijalankan, stempel waktu commit-nya c1
dan c2
mengikuti urutan transaksi linear, yang berarti bahwa semua efek operasi baca dan tulis Txn1
tampaknya
telah terjadi pada satu titik waktu (c1
), dan semua efek
operasi baca dan tulis Txn2
tampaknya telah terjadi pada satu titik waktu
(c2
). Selain itu, c1 < c2
(yang dijamin karena Txn1
dan
Txn2
melakukan operasi tulis; hal ini berlaku meskipun operasi tulis terjadi di mesin
yang berbeda), yang mengikuti urutan Txn1
yang terjadi sebelum Txn2
.
(Namun, jika Txn2
hanya melakukan operasi baca dalam transaksi, maka c1 <= c2
).
Operasi baca mengamati awalan histori commit; jika operasi baca melihat efek
Txn2
, operasi baca tersebut juga akan melihat efek Txn1
. Semua transaksi yang berhasil
di-commit memiliki properti ini.
Jaminan baca dan tulis
Jika panggilan untuk menjalankan transaksi gagal, jaminan baca dan tulis yang Anda miliki bergantung pada error yang menyebabkan panggilan commit yang mendasarinya gagal.
Misalnya, error seperti "Baris Tidak Ditemukan" atau "Baris Sudah Ada" berarti penulisan mutasi yang dibuffer mengalami beberapa error, misalnya baris yang dicoba diperbarui oleh klien tidak ada. Dalam hal ini, operasi baca dijamin konsisten, operasi tulis tidak diterapkan, dan tidak adanya baris juga dijamin konsisten dengan operasi baca.
Membatalkan operasi transaksi
Operasi baca asinkron dapat dibatalkan kapan saja oleh pengguna (misalnya, saat operasi tingkat yang lebih tinggi dibatalkan atau Anda memutuskan untuk menghentikan operasi baca berdasarkan hasil awal yang diterima dari operasi baca) tanpa memengaruhi operasi lain yang ada dalam transaksi.
Namun, meskipun Anda telah mencoba membatalkan operasi baca, Spanner tidak menjamin bahwa operasi baca benar-benar dibatalkan. Setelah Anda meminta pembatalan pembacaan, pembacaan tersebut masih dapat berhasil diselesaikan atau gagal dengan beberapa alasan lain (misalnya, Batalkan). Selain itu, pembacaan yang dibatalkan mungkin benar-benar menampilkan beberapa hasil kepada Anda, dan hasil yang mungkin tidak lengkap akan divalidasi sebagai bagian dari Commit transaksi.
Perhatikan bahwa tidak seperti operasi baca, membatalkan operasi Commit transaksi akan mengakibatkan pembatalan transaksi (kecuali jika transaksi telah Di-commit atau gagal karena alasan lain).
Performa
Mengunci
Spanner memungkinkan beberapa klien berinteraksi secara bersamaan dengan database yang sama. Untuk memastikan konsistensi beberapa transaksi serentak, Spanner menggunakan kombinasi kunci bersama dan kunci eksklusif untuk mengontrol akses ke data. Saat Anda melakukan operasi baca sebagai bagian dari transaksi, Spanner akan memperoleh kunci baca bersama, yang memungkinkan operasi baca lain tetap mengakses data hingga transaksi Anda siap di-commit. Saat transaksi Anda melakukan commit dan operasi tulis diterapkan, transaksi akan mencoba mengupgrade ke kunci eksklusif. Tindakan ini memblokir kunci baca bersama baru pada data, menunggu kunci baca bersama yang ada dihapus, lalu menempatkan kunci eksklusif untuk akses eksklusif ke data.
Catatan tentang kunci:
- Kunci diambil pada tingkat perincian baris dan kolom. Jika transaksi T1 telah mengunci kolom "A" dari baris "foo", dan transaksi T2 ingin menulis kolom "B" dari baris "foo", maka tidak ada konflik.
- Operasi tulis ke item data yang juga tidak membaca data yang ditulis (alias "tulis buta") tidak bertentangan dengan penulis buta lain dari item yang sama (stempel waktu commit setiap operasi tulis menentukan urutan penerapannya ke database). Konsekuensinya adalah Spanner hanya perlu mengupgrade ke kunci eksklusif jika Anda telah membaca data yang Anda tulis. Jika tidak, Spanner akan menggunakan kunci bersama yang disebut kunci bersama penulis.
- Saat melakukan pencarian baris di dalam transaksi baca-tulis, gunakan indeks sekunder untuk membatasi baris yang dipindai ke rentang yang lebih kecil. Hal ini menyebabkan Spanner mengunci lebih sedikit baris dalam tabel, sehingga memungkinkan modifikasi serentak pada baris di luar rentang.
Kunci tidak boleh digunakan untuk memastikan akses eksklusif ke resource di luar Spanner. Transaksi dapat dibatalkan karena beberapa alasan oleh Spanner, misalnya, saat mengizinkan data berpindah di seluruh resource komputasi instance. Jika transaksi dicoba lagi, baik secara eksplisit oleh kode aplikasi atau secara implisit oleh kode klien seperti driver JDBC Spanner, hanya dijamin bahwa kunci ditahan selama upaya yang benar-benar dilakukan.
Anda dapat menggunakan alat introspeksi Statistik kunci untuk menyelidiki konflik kunci di database.
Deteksi deadlock
Spanner mendeteksi kapan beberapa transaksi mungkin mengalami deadlock, dan
memaksa semua transaksi kecuali satu untuk dibatalkan. Misalnya, pertimbangkan
skenario berikut: transaksi Txn1
memegang kunci pada data A
dan menunggu
kunci pada data B
, dan Txn2
memegang kunci pada data B
dan menunggu
kunci pada data A
. Satu-satunya cara untuk membuat progres dalam situasi ini adalah dengan
membatalkan salah satu transaksi sehingga melepaskan kuncinya, yang memungkinkan transaksi
lainnya untuk dilanjutkan.
Spanner menggunakan algoritma "wound-wait" standar untuk menangani deteksi deadlock. Di balik layar, Spanner melacak usia setiap transaksi yang meminta kunci yang bertentangan. Hal ini juga memungkinkan transaksi lama membatalkan transaksi yang lebih baru (dengan "lama" berarti pembacaan, kueri, atau commit terlama transaksi terjadi lebih awal).
Dengan memberikan prioritas pada transaksi yang lebih lama, Spanner memastikan bahwa setiap transaksi memiliki peluang untuk memperoleh kunci pada akhirnya, setelah cukup lama sehingga memiliki prioritas lebih tinggi daripada transaksi lainnya. Misalnya, transaksi yang memperoleh kunci bersama pembaca dapat dibatalkan oleh transaksi lama yang memerlukan kunci bersama penulis.
Eksekusi terdistribusi
Spanner dapat menjalankan transaksi pada data yang mencakup beberapa server. Kemampuan ini memiliki biaya performa dibandingkan dengan transaksi satu server.
Jenis transaksi apa yang mungkin didistribusikan? Di balik layar, Spanner dapat membagi tanggung jawab untuk baris di database di banyak server. Baris dan baris yang sesuai dalam tabel yang diselingi biasanya ditayangkan oleh server yang sama, seperti dua baris dalam tabel yang sama dengan kunci yang berdekatan. Spanner dapat melakukan transaksi di seluruh baris di server yang berbeda; namun, sebagai aturan umum, transaksi yang memengaruhi banyak baris yang berlokasi sama lebih cepat dan lebih murah daripada transaksi yang memengaruhi banyak baris yang tersebar di seluruh database, atau di seluruh tabel besar.
Transaksi yang paling efisien di Spanner hanya menyertakan operasi baca dan tulis yang harus diterapkan secara atomik. Transaksi paling cepat jika semua pembacaan dan penulisan mengakses data di bagian yang sama dari ruang kunci.
Transaksi hanya baca
Selain mengunci transaksi baca-tulis, Spanner menawarkan transaksi hanya baca.
Gunakan transaksi hanya baca jika Anda perlu menjalankan lebih dari satu operasi baca pada stempel waktu yang sama. Jika Anda dapat mengekspresikan pembacaan menggunakan salah satu metode pembacaan tunggal Spanner, Anda harus menggunakan metode pembacaan tunggal tersebut. Performa penggunaan satu panggilan baca tersebut harus sebanding dengan performa satu pembacaan yang dilakukan dalam transaksi hanya baca.
Jika Anda membaca data dalam jumlah besar, pertimbangkan untuk menggunakan partisi guna membaca data secara paralel.
Karena transaksi hanya baca tidak menulis, transaksi tersebut tidak memegang kunci dan tidak memblokir transaksi lain. Transaksi hanya baca mengamati awalan histori commit transaksi yang konsisten, sehingga aplikasi Anda selalu mendapatkan data yang konsisten.
Properti
Transaksi hanya baca Spanner mengeksekusi serangkaian operasi baca pada satu titik waktu logis, baik dari perspektif transaksi hanya baca itu sendiri maupun dari perspektif pembaca dan penulis lain ke database Spanner. Artinya, transaksi hanya baca selalu mengamati status database yang konsisten pada titik yang dipilih dalam histori transaksi.
Antarmuka
Spanner menyediakan antarmuka untuk menjalankan kumpulan tugas dalam konteks transaksi hanya baca, dengan percobaan ulang untuk pembatalan transaksi.
Contoh
Berikut ini cara menggunakan transaksi hanya baca untuk mendapatkan data yang konsisten untuk dua operasi baca pada stempel waktu yang sama:
C++
C#
Go
Java
Node.js
PHP
Python
Ruby
Transaksi DML yang dipartisi
Dengan menggunakan Partitioned Data Manipulation Language (DML Berpartisi), Anda dapat mengeksekusi pernyataan UPDATE
dan DELETE
berskala besar tanpa mengalami batas transaksi atau mengunci seluruh tabel.
Spanner mempartisi ruang kunci dan menjalankan pernyataan DML di setiap
partisi dalam transaksi baca-tulis terpisah.
Anda menjalankan pernyataan DML dalam transaksi baca-tulis yang Anda buat secara eksplisit dalam kode. Untuk mengetahui informasi selengkapnya, lihat Menggunakan DML.
Properti
Anda hanya dapat menjalankan satu pernyataan DML yang Dipartisi dalam satu waktu, baik menggunakan metode library klien maupun Google Cloud CLI.
Transaksi yang dipartisi tidak mendukung commit atau rollback. Spanner segera mengeksekusi dan menerapkan pernyataan DML. Jika Anda membatalkan operasi, atau operasi gagal, Spanner akan membatalkan semua partisi yang dieksekusi dan tidak memulai partisi yang tersisa. Spanner tidak melakukan rollback pada partisi yang telah dieksekusi.
Antarmuka
Spanner menyediakan antarmuka untuk menjalankan satu pernyataan DML Partisi.
Contoh
Contoh kode berikut memperbarui kolom MarketingBudget
dari tabel Albums
.
C++
Anda menggunakan fungsi ExecutePartitionedDml()
untuk mengeksekusi pernyataan DML yang Dipartisi.
C#
Anda menggunakan metode ExecutePartitionedUpdateAsync()
untuk mengeksekusi pernyataan DML Berpartisi.
Go
Anda menggunakan metode PartitionedUpdate()
untuk mengeksekusi pernyataan DML Berpartisi.
Java
Anda menggunakan metode executePartitionedUpdate()
untuk mengeksekusi pernyataan DML Berpartisi.
Node.js
Anda menggunakan metode runPartitionedUpdate()
untuk mengeksekusi pernyataan DML Berpartisi.
PHP
Anda menggunakan metode executePartitionedUpdate()
untuk mengeksekusi pernyataan DML Berpartisi.
Python
Anda menggunakan metode execute_partitioned_dml()
untuk mengeksekusi pernyataan DML Berpartisi.
Ruby
Anda menggunakan metode execute_partitioned_update()
untuk mengeksekusi pernyataan DML Berpartisi.
Contoh kode berikut menghapus baris dari tabel Singers
, berdasarkan
kolom SingerId
.