Halaman ini menjelaskan transaksi di Spanner dan menyertakan kode contoh untuk menjalankan transaksi.
Pengantar
Transaksi di Spanner adalah sekumpulan operasi baca dan tulis yang dieksekusi secara atomik pada satu 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 perlu, commit dengan dua fase. Mengunci transaksi baca-tulis dapat dibatalkan, sehingga aplikasi harus mencoba lagi.
Hanya baca. Jenis transaksi ini memberikan jaminan konsistensi di beberapa pembacaan, tetapi tidak memungkinkan penulisan. Secara default, transaksi hanya baca dijalankan pada stempel waktu yang dipilih sistem yang menjamin konsistensi eksternal, tetapi transaksi juga dapat dikonfigurasi untuk dibaca pada stempel waktu di masa lalu. Transaksi hanya baca tidak perlu di-commit dan tidak terkunci. Selain itu, transaksi hanya baca mungkin menunggu hingga operasi tulis yang sedang berlangsung selesai sebelum dieksekusi.
DML Berpartisi. Jenis transaksi ini mengeksekusi pernyataan Bahasa Manipulasi Data (DML) sebagai DML Berpartisi. DML yang dipartisi dirancang untuk update dan penghapusan massal, terutama pembersihan dan pengisian ulang berkala. Jika Anda perlu melakukan commit dalam jumlah besar blind write, tetapi tidak memerlukan transaksi atomik, Anda dapat mengubah tabel Spanner secara massal menggunakan batch write. Untuk mengetahui informasi selengkapnya, lihat Mengubah data menggunakan batch operasi.
Halaman ini menjelaskan properti umum dan semantik transaksi di Spanner serta memperkenalkan antarmuka transaksi DML Terpartisi, operasi baca-tulis, dan DML Terpartisi di Spanner.
Transaksi baca-tulis
Berikut adalah skenario saat Anda harus menggunakan transaksi baca-tulis penguncian:
- Jika operasi tulis yang dilakukan bergantung pada hasil dari satu atau beberapa operasi baca, Anda harus melakukannya dan operasi baca tersebut dalam transaksi baca-tulis yang sama.
- Contoh: gandakan 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, penulisan tersebut harus dilakukan dalam transaksi baca-tulis yang sama.
- Contoh: transfer $200 dari akun A ke akun B. Kedua operasi tulis (satu penulisan untuk menurunkan A sebesar $200 dan yang lainnya untuk menaikkan B sebesar $200) dan pembacaan saldo rekening awal harus dilakukan dalam transaksi yang sama.
- Jika Anda mungkin melakukan satu atau beberapa operasi tulis, bergantung pada hasil dari satu atau beberapa operasi baca, Anda harus melakukan operasi tulis dan baca tersebut dalam transaksi baca-tulis yang sama, meskipun jika pada akhirnya tidak 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 penulisan.
Berikut adalah skenario di mana Anda tidak boleh menggunakan transaksi baca-tulis yang mengunci:
- Jika Anda hanya melakukan pembacaan, dan dapat mengekspresikan operasi baca menggunakan metode baca tunggal, Anda harus menggunakan metode baca tunggal tersebut atau transaksi hanya baca. Pembacaan tunggal tidak mengunci, tidak seperti transaksi baca-tulis.
Properti
Transaksi baca-tulis di Spanner mengeksekusi serangkaian pembacaan dan penulisan secara atomik pada satu titik waktu logis. Selain itu, stempel waktu saat transaksi baca-tulis dijalankan cocok dengan waktu aktual, dan urutan serialisasi cocok dengan urutan stempel waktu.
Mengapa menggunakan transaksi baca-tulis? Transaksi baca-tulis memberikan properti ACID dari database relasional (Faktanya, 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 pembacaan (atau kueri) dan penulisan:
- Semua operasi baca dalam transaksi yang menampilkan nilai menampilkan snapshot konsisten yang diambil pada stempel waktu commit transaksi.
- Baris atau rentang kosong tetap demikian pada waktu commit.
- Semua penulisan dalam transaksi di-commit pada stempel waktu commit transaksi.
- Penulisan tidak dapat dilihat oleh transaksi apa pun hingga transaksi tersebut di-commit.
Driver klien Spanner tertentu berisi logika percobaan ulang transaksi untuk menyamarkan error sementara, yang dilakukan dengan menjalankan ulang transaksi dan memvalidasi data yang diamati klien.
Akibatnya, semua pembacaan dan penulisan tampaknya terjadi pada satu titik waktu, baik dari perspektif transaksi itu sendiri maupun dari perspektif pembaca dan penulis lain terhadap database Spanner. Dengan kata lain, operasi baca dan tulis berakhir pada stempel waktu yang sama (lihat ilustrasinya di bagian Serialisabilitas dan konsistensi eksternal di bawah).
Transaksi yang hanya dibaca
Jaminan untuk transaksi baca-tulis yang hanya membaca serupa: semua bacaan dalam transaksi tersebut akan menampilkan data dari stempel waktu yang sama, bahkan untuk tidak adanya baris. Satu perbedaannya adalah jika Anda membaca data, lalu meng-commit transaksi baca-tulis tanpa penulisan apa pun, tidak ada jaminan bahwa data tidak akan berubah dalam database setelah pembacaan dan sebelum commit. Jika ingin mengetahui apakah data telah berubah sejak Anda membacanya terakhir, pendekatan terbaik adalah membacanya lagi (baik dalam transaksi baca-tulis, atau menggunakan pembacaan yang kuat). Selain itu, untuk efisiensi, jika Anda sudah tahu sebelumnya bahwa Anda hanya akan membaca dan tidak menulis, Anda harus menggunakan transaksi hanya baca, bukan transaksi baca-tulis.
Atomisitas, Konsistensi, Ketahanan
Selain properti Isolation, Spanner menyediakan Atomicity (jika salah satu penulisan dalam commit transaksi, semuanya di-commit), Konsistensi (database tetap dalam status konsisten setelah transaksi), dan Ketahanan (data yang di-commit tetap di-commit).
Manfaat properti ini
Karena properti ini, sebagai developer aplikasi, Anda dapat berfokus pada kebenaran setiap transaksi sendiri, tanpa perlu memikirkan cara melindungi eksekusinya dari transaksi lain yang mungkin dijalankan 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 mengerjakan data secara bersamaan dengan cara yang dapat menyebabkan deadlock, Spanner akan membatalkan salah satunya, sehingga transaksi lainnya dapat berlanjut. (Peristiwa sementara dalam Spanner yang lebih jarang dapat mengakibatkan pembatalan beberapa transaksi.) Karena transaksi bersifat atomik, transaksi yang dibatalkan tidak memiliki efek yang terlihat pada database. Oleh karena itu, transaksi harus dijalankan dengan mencoba lagi hingga berhasil.
Saat menggunakan transaksi di library klien Spanner, Anda menentukan isi transaksi (yaitu, pembacaan dan penulisan yang akan dilakukan di 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 terjadi error yang tidak dapat dicoba lagi.
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 dikunci oleh
Albums (1, 1)
dan telah meminta Anda untuk memindahkan $200.000 dari anggaran Albums
(2, 2)
, tetapi hanya jika uangnya tersedia dalam anggaran album tersebut. Anda harus
menggunakan transaksi baca-tulis penguncian untuk operasi ini, karena transaksi tersebut
dapat melakukan penulisan, bergantung pada hasil pembacaan.
Berikut 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 benar-benar terjadi secara paralel. Spanner menetapkan stempel waktu commit yang mencerminkan urutan transaksi yang dilakukan untuk menerapkan properti ini. Faktanya, Spanner menawarkan jaminan yang lebih kuat daripada serialisabilitas yang disebut konsistensi eksternal: transaksi dilakukan dalam urutan yang tercermin dalam stempel waktu commit ini, dan stempel waktu commit ini mencerminkan secara real time sehingga Anda dapat membandingkannya dengan smartwatch Anda. Operasi baca dalam transaksi melihat semua yang telah di-commit sebelum transaksi di-commit, dan operasi tulis terlihat oleh semua yang dimulai setelah transaksi di-commit.
Misalnya, pertimbangkan eksekusi dua transaksi seperti yang diilustrasikan dalam diagram di bawah ini:
Transaksi Txn1
yang berwarna biru membaca beberapa A
data, mem-buffer operasi tulis ke A
, lalu berhasil melakukan commit. Transaksi Txn2
yang berwarna hijau dimulai setelah Txn1
, membaca beberapa data B
, lalu membaca A
data. Karena Txn2
membaca nilai A
setelah Txn1
meng-commit penulisannya ke A
, Txn2
melihat efek penulisan Txn1
ke A
, meskipun Txn2
dimulai sebelum Txn1
selesai.
Meskipun ada beberapa tumpang tindih waktu saat Txn1
dan Txn2
dieksekusi, stempel waktu commitnya c1
dan c2
mengikuti urutan transaksi linear, yang berarti bahwa semua efek dari pembacaan dan penulisan Txn1
tampaknya terjadi pada satu titik waktu (c1
), dan semua efek dari operasi baca dan tulis Txn2
tampaknya terjadi pada satu titik waktu (c2
). Selain itu, c1 < c2
(yang dijamin adalah operasi tulis yang sama dengan Txn1
dan Txn2
yang terjadi pada mesin penulisan yang berbeda).Txn1
Txn2
(Namun, jika Txn2
hanya melakukan pembacaan dalam transaksi, selanjutnya c1 <= c2
).
Operasi baca mengamati awalan histori commit; jika operasi baca melihat efek
Txn2
, operasi baca juga akan melihat efek Txn1
. Semua transaksi yang melakukan commit
berhasil memiliki properti ini.
Jaminan operasi baca dan tulis
Jika panggilan untuk menjalankan transaksi gagal, pembacaan dan penulisan menjamin bahwa Anda bergantung pada error yang menyebabkan panggilan commit yang mendasarinya gagal.
Misalnya, error seperti "Row Not Found" atau "Row Sudah Ada" berarti penulisan mutasi yang di-buffer mengalami beberapa error, misalnya baris yang coba diperbarui oleh klien tidak ada. Dalam hal ini, operasi baca dijamin konsisten, operasi tulis tidak diterapkan, dan ketiadaan baris dijamin juga konsisten dengan pembacaan.
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 pembacaan berdasarkan hasil awal yang diterima dari pembacaan) tanpa memengaruhi operasi lain yang sudah ada dalam transaksi.
Namun, meskipun Anda telah mencoba membatalkan pembacaan, Spanner tidak menjamin bahwa pembacaan tersebut benar-benar dibatalkan. Setelah Anda meminta pembatalan pembacaan, operasi baca tersebut masih dapat berhasil diselesaikan atau gagal dengan beberapa alasan lain (misalnya, Batalkan). Selain itu, pembacaan yang dibatalkan tersebut mungkin menampilkan beberapa hasil kepada Anda, dan hasil yang mungkin tidak lengkap tersebut akan divalidasi sebagai bagian dari Commit transaksi.
Perlu diperhatikan bahwa tidak seperti pembacaan, membatalkan operasi Commit transaksi akan mengakibatkan pembatalan transaksi (kecuali jika transaksi tersebut telah Berkomitmen atau gagal dengan alasan lain).
Performa
Mengunci
Spanner dapat digunakan beberapa klien untuk 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 pembacaan sebagai bagian dari transaksi, Spanner akan memperoleh kunci baca bersama, sehingga operasi baca lainnya tetap dapat mengakses data hingga transaksi Anda siap di-commit. Saat transaksi Anda di-commit dan operasi tulis diterapkan, transaksi akan mencoba mengupgrade ke kunci eksklusif. Fitur 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 smart lock:
- 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 akan ada konflik.
- Menulis ke item data yang juga tidak membaca data yang sedang ditulis (alias "blind write") tidak bertentangan dengan penulis tunanetra lainnya dari item yang sama (stempel waktu commit setiap penulisan menentukan urutan penerapannya ke database). Konsekuensinya, Spanner hanya perlu diupgrade ke kunci eksklusif jika Anda telah membaca data yang ditulis. 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 ke 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 seperti, misalnya, saat mengizinkan data berpindah di resource komputasi instance. Jika transaksi dicoba lagi, baik secara eksplisit dengan kode aplikasi maupun secara implisit oleh kode klien seperti driver JDBC Spanner, hanya ada jaminan bahwa kunci ditahan selama upaya yang benar-benar dilakukan.
Anda dapat menggunakan alat introspeksi Statistik kunci untuk menyelidiki konflik kunci di database Anda.
Deteksi deadlock
Spanner mendeteksi ketika beberapa transaksi mungkin mengalami deadlock, dan memaksa semua transaksi kecuali satu transaksi untuk dibatalkan. Misalnya, pertimbangkan skenario berikut: transaksi Txn1
menyimpan kunci pada kumpulan data A
dan menunggu kunci pada kumpulan data B
, serta Txn2
menyimpan kunci pada kumpulan data B
dan menunggu kunci pada kumpulan data A
. Satu-satunya cara untuk membuat progres dalam situasi ini adalah dengan
membatalkan salah satu transaksi agar melepaskan kuncinya, sehingga transaksi
lainnya dapat dilanjutkan.
Spanner menggunakan algoritma "wound-wait" standar untuk menangani deteksi deadlock. Di balik layar, Spanner memantau usia setiap transaksi yang meminta kunci yang bertentangan. Hal ini juga memungkinkan transaksi lama untuk membatalkan transaksi yang lebih lama ("yang lebih lama" berarti pembacaan, kueri, atau commit paling awal dari transaksi tersebut terjadi lebih cepat).
Dengan memberikan prioritas pada transaksi yang lebih lama, Spanner memastikan bahwa setiap transaksi memiliki peluang untuk memperoleh kunci pada akhirnya, setelah cukup lama untuk memiliki prioritas yang 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. Kekuatan ini memiliki biaya performa dibandingkan dengan transaksi server tunggal.
Jenis transaksi apa yang mungkin didistribusikan? Di balik layar, Spanner dapat membagi tanggung jawab untuk baris dalam {i>database<i} ke banyak server. Baris dan baris yang sesuai dalam tabel sisipan biasanya disalurkan oleh server yang sama, seperti dua baris dalam tabel yang sama dengan kunci di dekatnya. Spanner dapat melakukan transaksi lintas baris pada server yang berbeda. Namun, sebagai pedoman, transaksi yang memengaruhi banyak baris yang ditempatkan bersama akan lebih cepat dan lebih murah dibandingkan transaksi yang memengaruhi banyak baris yang tersebar di seluruh database, atau di seluruh tabel besar.
Transaksi yang paling efisien di Spanner hanya menyertakan pembacaan dan penulisan yang harus diterapkan secara atomik. Transaksi akan berjalan paling cepat jika semua membaca dan menulis mengakses data di bagian yang sama dalam ruang kunci.
Transaksi hanya baca
Selain mengunci transaksi baca-tulis, Spanner juga menawarkan transaksi hanya baca.
Gunakan transaksi hanya baca jika Anda perlu mengeksekusi lebih dari satu operasi baca pada stempel waktu yang sama. Jika Anda dapat mengekspresikan operasi baca menggunakan salah satu metode baca tunggal Spanner, Anda harus menggunakan metode baca 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 untuk membaca data secara paralel.
Karena transaksi hanya-baca tidak ditulis, transaksi tersebut tidak menahan kunci dan tidak memblokir transaksi lain. Transaksi hanya baca mengamati awalan yang konsisten dari histori commit transaksi, sehingga aplikasi Anda selalu mendapatkan data yang konsisten.
Properti
Transaksi hanya baca Spanner mengeksekusi sekumpulan operasi baca pada satu titik waktu logis, baik dari perspektif transaksi hanya baca itu sendiri maupun dari perspektif pembaca dan penulis lain hingga database Spanner. Ini berarti bahwa transaksi hanya baca selalu mengamati status database yang konsisten pada titik yang dipilih dalam histori transaksi.
Antarmuka
Spanner menyediakan antarmuka untuk menjalankan isi tugas dalam konteks transaksi hanya baca, dengan percobaan ulang untuk pembatalan transaksi.
Contoh
Berikut ini cara menggunakan transaksi hanya baca guna mendapatkan data yang konsisten untuk dua pembacaan pada stempel waktu yang sama:
C++
C#
Go
Java
Node.js
PHP
Python
Ruby
Transaksi DML yang dipartisi
Dengan Bahasa Manipulasi Data Terpartisi
(DML Berpartisi), Anda dapat mengeksekusi pernyataan UPDATE
dan DELETE
skala besar
tanpa menemui batas transaksi atau mengunci seluruh tabel.
Spanner mempartisi ruang kunci dan menjalankan pernyataan DML pada setiap partisi dalam transaksi baca-tulis terpisah.
Anda menjalankan pernyataan DML dalam transaksi baca-tulis yang Anda buat secara eksplisit dalam kode Anda. Untuk mengetahui informasi selengkapnya, lihat Menggunakan DML.
Properti
Anda hanya dapat menjalankan satu pernyataan DML yang dipartisi pada satu waktu, baik saat menggunakan metode library klien maupun Google Cloud CLI.
Transaksi yang dipartisi tidak mendukung commit atau rollback. Spanner akan 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 partisi apa pun yang telah dieksekusi.
Antarmuka
Spanner menyediakan antarmuka untuk menjalankan pernyataan DML Terpartisi.
Contoh
Contoh kode berikut memperbarui kolom MarketingBudget
pada tabel Albums
.
C++
Anda menggunakan fungsi ExecutePartitionedDml()
untuk mengeksekusi pernyataan DML yang dipartisi.
C#
Anda menggunakan metode ExecutePartitionedUpdateAsync()
untuk mengeksekusi pernyataan DML yang dipartisi.
Go
Anda menggunakan metode PartitionedUpdate()
untuk mengeksekusi pernyataan DML yang dipartisi.
Java
Anda menggunakan metode executePartitionedUpdate()
untuk mengeksekusi pernyataan DML yang dipartisi.
Node.js
Anda menggunakan metode runPartitionedUpdate()
untuk mengeksekusi pernyataan DML yang dipartisi.
PHP
Anda menggunakan metode executePartitionedUpdate()
untuk mengeksekusi pernyataan DML yang dipartisi.
Python
Anda menggunakan metode execute_partitioned_dml()
untuk mengeksekusi pernyataan DML yang dipartisi.
Ruby
Anda menggunakan metode execute_partitioned_update()
untuk mengeksekusi pernyataan DML yang dipartisi.
Contoh kode berikut menghapus baris dari tabel Singers
, berdasarkan kolom
SingerId
.