Konkurensi optimis
Berlaku untuk: .NET Framework .NET .NET Standard
Dalam lingkungan multiuser, ada dua model untuk memperbarui data dalam database: konkurensi optimis dan konkurensi pesimis. Objek DataSet dirancang untuk mendorong penggunaan konkurensi optimis untuk aktivitas yang berjalan lama, seperti data jarak jauh dan interaksi dengan data.
Konkurensi pesimis melibatkan penguncian baris pada sumber data untuk mencegah pengguna lain memodifikasi data dengan cara yang memengaruhi pengguna saat ini. Dalam model pesimis, ketika pengguna melakukan tindakan yang menyebabkan kunci diterapkan, pengguna lain tidak dapat melakukan tindakan yang akan bertentangan dengan kunci sampai pemilik kunci melepaskannya. Model ini terutama digunakan di lingkungan di mana ada perebutan data yang berat, sehingga biaya melindungi data dengan kunci lebih sedikit daripada biaya memutar kembali transaksi jika terjadi konflik konkurensi.
Oleh karena itu, dalam model konkurensi pesimis, pengguna yang memperbarui baris membuat kunci. Sampai pengguna menyelesaikan pembaruan dan melepaskan kunci, tidak ada orang lain yang dapat mengubah baris itu. Untuk alasan ini, konkurensi pesimistis paling baik diterapkan ketika waktu penguncian akan singkat, seperti dalam pemrosesan catatan terprogram. Konkurensi pesimis bukanlah opsi yang dapat diskalakan saat pengguna berinteraksi dengan data dan menyebabkan rekaman dikunci untuk jangka waktu yang relatif lama.
Catatan
Jika Anda perlu memperbarui beberapa baris dalam operasi yang sama, maka membuat transaksi adalah opsi yang lebih terukur daripada menggunakan penguncian pesimis.
Sebaliknya, pengguna yang menggunakan konkurensi optimis tidak mengunci baris saat membacanya. Saat pengguna ingin memperbarui baris, aplikasi harus menentukan apakah pengguna lain telah mengubah baris sejak dibaca. Konkurensi optimis umumnya digunakan di lingkungan dengan pertentangan data yang rendah. Konkurensi yang optimis meningkatkan performa karena tidak diperlukan penguncian catatan, dan penguncian catatan memerlukan sumber daya server tambahan. Juga, untuk mempertahankan kunci rekaman, koneksi terus-menerus ke server database diperlukan. Karena ini tidak terjadi dalam model konkurensi optimis, koneksi ke server bebas untuk melayani lebih banyak klien dalam waktu yang lebih singkat.
Dalam model konkurensi optimis, pelanggaran dianggap telah terjadi jika, setelah pengguna menerima nilai dari database, pengguna lain memodifikasi nilai sebelum pengguna pertama mencoba mengubahnya. Cara server mengatasi pelanggaran konkurensi paling baik ditunjukkan dengan terlebih dahulu menjelaskan contoh berikut.
Tabel berikut mengikuti contoh konkurensi optimis.
Pada pukul 1:00 siang, User1 membaca baris dari database dengan nilai-nilai berikut:
Nama Belakang CustID Nama Depan
101 Smith Bob
Nama kolom | Nilai asli | Nilai saat ini | Nilai dalam database |
---|---|---|---|
ID Pelanggan | 101 | 101 | 101 |
LastName | Smith | Smith | Smith |
FirstName | Bob | Bob | Bob |
Pada 13:01, User2 membaca baris yang sama.
Pada 13:03, User2 mengubah FirstName dari "Bob" menjadi "Robert" dan memperbarui database.
Nama kolom | Nilai asli | Nilai saat ini | Nilai dalam database |
---|---|---|---|
ID Pelanggan | 101 | 101 | 101 |
LastName | Smith | Smith | Smith |
FirstName | Bob | Robert | Bob |
Pembaruan berhasil karena nilai dalam database pada saat pembaruan cocok dengan nilai asli yang dimiliki User2.
Pada 13:05, User1 mengubah nama depan "Bob" menjadi "James" dan mencoba memperbarui baris.
Nama kolom | Nilai asli | Nilai saat ini | Nilai dalam database |
---|---|---|---|
ID Pelanggan | 101 | 101 | 101 |
LastName | Smith | Smith | Smith |
FirstName | Bob | James | Robert |
Pada titik ini, User1 mengalami pelanggaran serentak optimis karena nilai dalam database ("Robert") tidak lagi cocok dengan nilai asli yang diharapkan User1 ("Bob"). Pelanggaran konkurensi hanya memberi tahu Anda bahwa pembaruan gagal. Keputusan sekarang perlu dibuat apakah akan menimpa perubahan yang diberikan oleh User2 dengan perubahan yang diberikan oleh User1, atau membatalkan perubahan oleh User1.
Pengujian untuk pelanggaran konkurensi optimis
Ada beberapa teknik untuk menguji pelanggaran konkurensi optimis. Salah satunya melibatkan menyertakan kolom cap waktu dalam tabel.
Basis data biasanya menyediakan fungsionalitas stempel waktu yang dapat digunakan untuk mengidentifikasi tanggal dan waktu saat catatan terakhir diperbarui. Dengan menggunakan teknik ini, kolom stempel waktu disertakan dalam definisi tabel. Setiap kali catatan diperbarui, stempel waktu diperbarui untuk mencerminkan tanggal dan waktu saat ini.
Dalam tes untuk pelanggaran konkurensi optimis, kolom stempel waktu dikembalikan dengan kueri apa pun dari konten tabel. Saat pembaruan dicoba, nilai stempel waktu dalam database dibandingkan dengan nilai stempel waktu asli yang terkandung dalam baris yang dimodifikasi. Jika cocok, pembaruan dilakukan dan kolom stempel waktu diperbarui dengan waktu saat ini untuk mencerminkan pembaruan. Jika tidak cocok, pelanggaran konkurensi optimis telah terjadi.
Teknik lain untuk menguji pelanggaran konkurensi optimis adalah untuk memverifikasi bahwa semua nilai kolom asli dalam satu baris masih cocok dengan yang ditemukan dalam database. Misalnya, pertimbangkan kueri di bawah ini:
SELECT Col1, Col2, Col3 FROM Table1
Untuk menguji pelanggaran konkurensi optimis saat memperbarui baris di Table1, Anda akan mengeluarkan pernyataan UPDATE berikut:
UPDATE Table1 Set Col1 = @NewCol1Value,
Set Col2 = @NewCol2Value,
Set Col3 = @NewCol3Value
WHERE Col1 = @OldCol1Value AND
Col2 = @OldCol2Value AND
Col3 = @OldCol3Value
Selama nilai asli cocok dengan nilai dalam database, pembaruan dilakukan. Jika nilai telah diubah, pembaruan tidak akan mengubah baris karena klausa WHERE tidak akan menemukan kecocokan.
Perhatikan bahwa disarankan untuk selalu mengembalikan nilai kunci utama unik dalam kueri Anda. Jika tidak, pernyataan UPDATE sebelumnya dapat memperbarui lebih dari satu baris, yang mungkin bukan maksud Anda.
Jika kolom di sumber data Anda mengizinkan nol, Anda mungkin perlu memperluas klausa WHERE Anda untuk memeriksa referensi nol yang cocok di tabel lokal Anda dan di sumber data. Misalnya, pernyataan UPDATE berikut memverifikasi bahwa referensi nol di baris lokal masih cocok dengan referensi nol di sumber data, atau nilai di baris lokal masih cocok dengan nilai di sumber data.
UPDATE Table1 Set Col1 = @NewVal1
WHERE (@OldVal1 IS NULL AND Col1 IS NULL) OR Col1 = @OldVal1
Anda juga dapat memilih untuk menerapkan kriteria yang tidak terlalu ketat saat menggunakan model konkurensi optimis. Misalnya, hanya menggunakan kolom kunci utama dalam klausa WHERE menyebabkan data ditimpa terlepas dari apakah kolom lain telah diperbarui sejak kueri terakhir. Anda juga dapat menerapkan klausa WHERE hanya untuk kolom tertentu, sehingga data akan ditimpa kecuali bidang tertentu telah diperbarui sejak terakhir kali ditanyakan.
Peristiwa DataAdapter.RowUpdated
Peristiwa RowUpdated dari objek DataAdapter dapat digunakan bersama dengan teknik yang dijelaskan sebelumnya, untuk memberikan pemberitahuan ke aplikasi Anda tentang pelanggaran serentak optimis. RowUpdated terjadi setelah setiap upaya memperbarui baris Dimodifikasi dari DataSet. Ini memungkinkan Anda untuk menambahkan kode penanganan khusus, termasuk pemrosesan saat pengecualian terjadi, menambahkan informasi kesalahan kustom, menambahkan logika coba lagi, dan seterusnya.
Objek RowUpdatedEventArgs mengembalikan properti RecordsAffected yang berisi jumlah baris yang terpengaruh oleh perintah pembaruan tertentu untuk baris yang dimodifikasi dalam tabel. Dengan mengatur perintah pembaruan untuk menguji konkurensi optimis, properti RecordsAffected akan, sebagai hasilnya, mengembalikan nilai 0 ketika pelanggaran konkurensi optimis telah terjadi, karena tidak ada catatan yang diperbarui. Jika ini masalahnya, pengecualian dilemparkan.
Peristiwa RowUpdated memungkinkan Anda menangani kejadian ini dan menghindari pengecualian dengan mengatur nilai RowUpdatedEventArgs.Status yang sesuai, seperti UpdateStatus.SkipCurrentRow. Untuk informasi selengkapnya tentang peristiwa RowUpdated, lihat Menangani Peristiwa DataAdapter.
Secara opsional, Anda dapat mengatur DataAdapter.ContinueUpdateOnError ke true, sebelum memanggil Update, dan menanggapi informasi kesalahan yang disimpan di properti RowError baris tertentu saat Pembaruan selesai. Untuk informasi selengkapnya, lihat Informasi Kesalahan Baris.
Contoh konkurensi optimis
Berikut ini adalah contoh sederhana yang mengatur UpdateCommand dari DataAdapter untuk menguji konkurensi optimis, kemudian menggunakan peristiwa RowUpdated untuk menguji pelanggaran optimistis. Saat ditemukan pelanggaran konkurensi optimis, aplikasi mengatur RowError dari baris tempat pembaruan dikeluarkan untuk mencerminkan pelanggaran konkurensi optimis.
Perhatikan bahwa nilai parameter yang diteruskan ke klausa WHERE dari perintah UPDATE dipetakan ke nilai Asli dari kolomnya masing-masing.
using System;
using System.Data;
using Microsoft.Data.SqlClient;
class Program
{
static void Main(string[] args)
{
string connectionString = "Data Source = localhost; Integrated Security = true; Initial Catalog = Northwind";
using (SqlConnection connection = new SqlConnection(connectionString))
{
// Assumes connection is a valid SqlConnection.
SqlDataAdapter adapter = new SqlDataAdapter(
"SELECT CustomerID, CompanyName FROM Customers ORDER BY CustomerID",
connection);
// The Update command checks for optimistic concurrency violations
// in the WHERE clause.
adapter.UpdateCommand = new SqlCommand("UPDATE Customers Set CustomerID = @CustomerID, CompanyName = @CompanyName " +
"WHERE CustomerID = @oldCustomerID AND CompanyName = @oldCompanyName", connection);
adapter.UpdateCommand.Parameters.Add(
"@CustomerID", SqlDbType.NChar, 5, "CustomerID");
adapter.UpdateCommand.Parameters.Add(
"@CompanyName", SqlDbType.NVarChar, 30, "CompanyName");
// Pass the original values to the WHERE clause parameters.
SqlParameter parameter = adapter.UpdateCommand.Parameters.Add(
"@oldCustomerID", SqlDbType.NChar, 5, "CustomerID");
parameter.SourceVersion = DataRowVersion.Original;
parameter = adapter.UpdateCommand.Parameters.Add(
"@oldCompanyName", SqlDbType.NVarChar, 30, "CompanyName");
parameter.SourceVersion = DataRowVersion.Original;
// Add the RowUpdated event handler.
adapter.RowUpdated += new SqlRowUpdatedEventHandler(OnRowUpdated);
DataSet dataSet = new DataSet();
adapter.Fill(dataSet, "Customers");
// Modify the DataSet contents.
adapter.Update(dataSet, "Customers");
foreach (DataRow dataRow in dataSet.Tables["Customers"].Rows)
{
if (dataRow.HasErrors)
Console.WriteLine(dataRow[0] + "\n" + dataRow.RowError);
}
}
}
protected static void OnRowUpdated(object sender, SqlRowUpdatedEventArgs args)
{
if (args.RecordsAffected == 0)
{
args.Row.RowError = "Optimistic Concurrency Violation Encountered";
args.Status = UpdateStatus.SkipCurrentRow;
}
}
}