-
Anda tidak boleh memperbarui 10k baris dalam satu set kecuali Anda yakin bahwa operasi tersebut mendapatkan Penguncian Halaman (karena beberapa baris per halaman menjadi bagian dari
UPDATE
operasi). Masalahnya adalah bahwa Eskalasi Penguncian (dari kunci Baris atau Halaman ke Tabel) terjadi pada 5000 kunci . Jadi paling aman untuk menyimpannya di bawah 5000, untuk berjaga-jaga jika operasinya menggunakan Kunci Baris. -
Anda seharusnya tidak menggunakan SET ROWCOUNT untuk membatasi jumlah baris yang akan dimodifikasi. Ada dua masalah di sini:
-
Itu tidak digunakan lagi sejak SQL Server 2005 dirilis (11 tahun yang lalu):
Menggunakan SET ROWCOUNT tidak akan memengaruhi pernyataan DELETE, INSERT, dan UPDATE dalam rilis SQL Server mendatang. Hindari penggunaan SET ROWCOUNT dengan pernyataan DELETE, INSERT, dan UPDATE dalam pekerjaan pengembangan baru, dan rencanakan untuk memodifikasi aplikasi yang saat ini menggunakannya. Untuk perilaku serupa, gunakan sintaks TOP
-
Ini dapat memengaruhi lebih dari sekadar pernyataan yang Anda hadapi:
Menyetel opsi SET ROWCOUNT menyebabkan sebagian besar pernyataan Transact-SQL berhenti memproses ketika telah dipengaruhi oleh jumlah baris yang ditentukan. Ini termasuk pemicu. Opsi ROWCOUNT tidak mempengaruhi kursor dinamis, tetapi membatasi baris kunci dan kursor tidak sensitif. Opsi ini harus digunakan dengan hati-hati.
Sebagai gantinya, gunakan
TOP ()
klausa. -
-
Tidak ada tujuan melakukan transaksi eksplisit di sini. Ini memperumit kode dan Anda tidak memiliki penanganan untuk
ROLLBACK
, yang bahkan tidak diperlukan karena setiap pernyataan adalah transaksinya sendiri (yaitu komit otomatis). -
Dengan asumsi Anda menemukan alasan untuk menyimpan transaksi eksplisit, maka Anda tidak memiliki
TRY
/CATCH
struktur. Silakan lihat jawaban saya di DBA.StackExchange untukTRY
/CATCH
template yang menangani transaksi:Apakah kami diharuskan untuk menangani Transaksi dalam Kode C# serta dalam prosedur Store
Saya menduga bahwa yang asli WHERE
klausa tidak ditampilkan dalam kode contoh di Pertanyaan, jadi hanya mengandalkan apa yang telah ditampilkan, lebih baik modelnya adalah:
DECLARE @Rows INT,
@BatchSize INT; -- keep below 5000 to be safe
SET @BatchSize = 2000;
SET @Rows = @BatchSize; -- initialize just to enter the loop
BEGIN TRY
WHILE (@Rows = @BatchSize)
BEGIN
UPDATE TOP (@BatchSize) tab
SET tab.Value = 'abc1'
FROM TableName tab
WHERE tab.Parameter1 = 'abc'
AND tab.Parameter2 = 123
AND tab.Value <> 'abc1' COLLATE Latin1_General_100_BIN2;
-- Use a binary Collation (ending in _BIN2, not _BIN) to make sure
-- that you don't skip differences that compare the same due to
-- insensitivity of case, accent, etc, or linguistic equivalence.
SET @Rows = @@ROWCOUNT;
END;
END TRY
BEGIN CATCH
RAISERROR(stuff);
RETURN;
END CATCH;
Dengan menguji @Rows
terhadap @BatchSize
, Anda dapat menghindari UPDATE
terakhir itu kueri (dalam banyak kasus) karena set terakhir biasanya beberapa baris kurang dari @BatchSize
, dalam hal ini kami tahu bahwa tidak ada lagi yang harus diproses (yang Anda lihat dalam output yang ditunjukkan dalam jawaban Anda). Hanya dalam kasus di mana set baris terakhir sama dengan @BatchSize
apakah kode ini akan menjalankan UPDATE
terakhir mempengaruhi 0 baris.
Saya juga menambahkan kondisi ke WHERE
klausa untuk mencegah baris yang telah diperbarui agar tidak diperbarui lagi.
CATATAN TENTANG KINERJA
Saya menekankan "lebih baik" di atas (seperti dalam, "ini adalah lebih baik model") karena ini memiliki beberapa peningkatan pada kode asli OP, dan berfungsi dengan baik dalam banyak kasus, tetapi tidak sempurna untuk semua kasus. Untuk tabel setidaknya dengan ukuran tertentu (yang bervariasi karena beberapa faktor jadi saya bisa' t lebih spesifik), kinerja akan menurun karena ada lebih sedikit baris yang harus diperbaiki jika:
- tidak ada indeks untuk mendukung kueri, atau
- ada indeks, tetapi setidaknya ada satu kolom di
WHERE
klausa adalah tipe data string yang tidak menggunakan susunan biner, oleh karena ituCOLLATE
klausa ditambahkan ke kueri di sini untuk memaksa pemeriksaan biner, dan hal itu akan membuat indeks tidak valid (untuk kueri khusus ini).
Inilah situasi yang dihadapi @mikesigs, sehingga membutuhkan pendekatan yang berbeda. Metode yang diperbarui menyalin ID untuk semua baris yang akan diperbarui ke tabel sementara, lalu menggunakan tabel temp tersebut untuk INNER JOIN
ke tabel yang diperbarui pada kolom kunci indeks berkerumun. (Penting untuk menangkap dan bergabung di indeks berkerumun kolom, apakah itu kolom kunci utama atau bukan!).
Silakan lihat jawaban @mikesigs di bawah ini untuk detailnya. Pendekatan yang ditunjukkan dalam jawaban itu adalah pola yang sangat efektif yang telah saya gunakan sendiri dalam banyak kesempatan. Satu-satunya perubahan yang akan saya buat adalah:
- Buat
#targetIds
secara eksplisit tabel daripada menggunakanSELECT INTO...
- Untuk
#targetIds
tabel, mendeklarasikan kunci utama berkerumun pada kolom. - Untuk
#batchIds
tabel, mendeklarasikan kunci utama berkerumun pada kolom. - Untuk memasukkan ke dalam
#targetIds
, gunakanINSERT INTO #targetIds (column_name(s)) SELECT
dan hapusORDER BY
karena tidak perlu.
Jadi, jika Anda tidak memiliki indeks yang dapat digunakan untuk operasi ini, dan untuk sementara tidak dapat membuat indeks yang benar-benar berfungsi (indeks yang difilter mungkin berfungsi, tergantung pada WHERE
Anda klausa untuk UPDATE
query), lalu coba pendekatan yang ditunjukkan dalam jawaban @mikesigs (dan jika Anda menggunakan solusi itu, beri suara lebih tinggi).