Seperti yang ditulis Paul:Tidak, ini tidak aman , yang ingin saya tambahkan bukti empiris:Buat tabel Table_1
dengan satu bidang ID
dan satu record dengan nilai 0
. Kemudian jalankan kode berikut secara bersamaan di dua jendela kueri Management Studio :
declare @counter int
set @counter = 0
while @counter < 1000
begin
set @counter = @counter + 1
INSERT INTO Table_1
SELECT MAX(ID) + 1 FROM Table_1
end
Kemudian jalankan
SELECT ID, COUNT(*) FROM Table_1 GROUP BY ID HAVING COUNT(*) > 1
Di SQL Server 2008, satu ID saya (662
) dibuat dua kali. Jadi, tingkat isolasi default yang diterapkan pada pernyataan tunggal adalah tidak cukup.
EDIT:Jelas, membungkus INSERT
dengan BEGIN TRANSACTION
dan COMMIT
tidak akan memperbaikinya, karena tingkat isolasi default untuk transaksi masih READ COMMITTED
, yang tidak cukup. Perhatikan bahwa menyetel tingkat isolasi transaksi ke REPEATABLE READ
adalah juga tidak cukup. Satu-satunya cara untuk membuat kode di atas aman adalah menambahkan
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
di atas. Namun, ini terkadang menyebabkan kebuntuan dalam pengujian saya.
EDIT:Satu-satunya solusi yang saya temukan yang aman dan tidak menghasilkan kebuntuan (setidaknya dalam pengujian saya) adalah secara eksplisit mengunci tabel secara eksklusif (tingkat isolasi transaksi default sudah cukup di sini). Hati-hati meskipun; solusi ini mungkin membunuh kinerja:
...loop stuff...
BEGIN TRANSACTION
SELECT * FROM Table_1 WITH (TABLOCKX, HOLDLOCK) WHERE 1=0
INSERT INTO Table_1
SELECT MAX(ID) + 1 FROM Table_1
COMMIT
...loop end...