Jika Anda tidak memelihara meja penghitung, ada dua opsi. Dalam transaksi, pertama pilih MAX(seq_id)
dengan salah satu petunjuk tabel berikut:
WITH(TABLOCKX, HOLDLOCK)
WITH(ROWLOCK, XLOCK, HOLDLOCK)
TABLOCKX + HOLDLOCK
agak berlebihan. Ini memblokir pernyataan pilih biasa, yang dapat dianggap berat walaupun transaksinya kecil.
Sebuah ROWLOCK, XLOCK, HOLDLOCK
petunjuk tabel mungkin merupakan ide yang lebih baik (tetapi:baca alternatif dengan tabel penghitung lebih lanjut). Keuntungannya adalah tidak memblokir pernyataan pilih biasa, yaitu ketika pernyataan pilih tidak muncul dalam SERIALIZABLE
transaksi, atau ketika pernyataan pilih tidak memberikan petunjuk tabel yang sama. Menggunakan ROWLOCK, XLOCK, HOLDLOCK
akan tetap memblokir pernyataan penyisipan.
Tentu saja Anda perlu memastikan bahwa tidak ada bagian lain dari program Anda yang memilih MAX(seq_id)
tanpa petunjuk tabel ini (atau di luar SERIALIZABLE
transaksi) dan kemudian gunakan nilai ini untuk menyisipkan baris.
Perhatikan bahwa tergantung pada jumlah baris yang dikunci dengan cara ini, ada kemungkinan bahwa SQL Server akan meningkatkan kunci menjadi kunci tabel. Baca selengkapnya tentang eskalasi penguncian di sini .
Prosedur penyisipan menggunakan WITH(ROWLOCK, XLOCK, HOLDLOCK)
akan terlihat sebagai berikut:
DECLARE @target_model INT=3;
DECLARE @part VARCHAR(128)='Spine';
BEGIN TRY
BEGIN TRANSACTION;
DECLARE @max_seq INT=(SELECT MAX(seq) FROM dbo.table_seq WITH(ROWLOCK,XLOCK,HOLDLOCK) WHERE [email protected]_model);
IF @max_seq IS NULL SET @max_seq=0;
INSERT INTO dbo.table_seq(part,seq,model)VALUES(@part,@max_seq+1,@target_model);
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
END CATCH
Alternatif dan mungkin ide yang lebih baik adalah memiliki penghitung meja, dan berikan petunjuk tabel ini di meja penghitung. Tabel ini akan terlihat seperti berikut:
CREATE TABLE dbo.counter_seq(model INT PRIMARY KEY, seq_id INT);
Anda kemudian akan mengubah prosedur penyisipan sebagai berikut:
DECLARE @target_model INT=3;
DECLARE @part VARCHAR(128)='Spine';
BEGIN TRY
BEGIN TRANSACTION;
DECLARE @new_seq INT=(SELECT seq FROM dbo.counter_seq WITH(ROWLOCK,XLOCK,HOLDLOCK) WHERE [email protected]_model);
IF @new_seq IS NULL
BEGIN SET @new_seq=1; INSERT INTO dbo.counter_seq(model,seq)VALUES(@target_model,@new_seq); END
ELSE
BEGIN SET @new_seq+=1; UPDATE dbo.counter_seq SET [email protected]_seq WHERE [email protected]_model; END
INSERT INTO dbo.table_seq(part,seq,model)VALUES(@part,@new_seq,@target_model);
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
END CATCH
Keuntungannya adalah lebih sedikit kunci baris yang digunakan (yaitu satu kunci baris per model di dbo.counter_seq
), dan eskalasi kunci tidak dapat mengunci seluruh dbo.table_seq
tabel sehingga memblokir pernyataan tertentu.
Anda dapat menguji semua ini dan melihat efeknya sendiri, dengan menempatkan WAITFOR DELAY '00:01:00'
setelah memilih urutan dari counter_seq
, dan mengutak-atik tabel di tab SSMS kedua.
PS1:Menggunakan ROW_NUMBER() OVER (PARTITION BY model ORDER BY ID)
bukanlah cara yang baik. Jika baris dihapus/ditambahkan, atau ID diubah, urutannya akan berubah (pertimbangkan ID faktur yang tidak boleh berubah). Juga dalam hal kinerja harus menentukan nomor baris dari semua baris sebelumnya saat mengambil satu baris adalah ide yang buruk.
PS2:Saya tidak akan pernah menggunakan sumber daya luar untuk menyediakan penguncian, ketika SQL Server sudah menyediakan penguncian melalui tingkat isolasi atau petunjuk tabel yang halus.