Pernyataan individu - DML, DDL, dll - adalah transaksi sendiri. Jadi ya, setelah setiap iterasi dari loop (secara teknis: setelah setiap pernyataan), apa pun UPDATE
pernyataan yang diubah telah dikomit secara otomatis.
Tentu saja, selalu ada pengecualian, bukan? Dimungkinkan untuk mengaktifkan Transaksi Implisit melalui SET IMPLICIT_TRANSACTIONS , dalam hal ini UPDATE
pernyataan pertama akan memulai transaksi yang Anda harus COMMIT
atau ROLLBACK
pada akhirnya. Ini adalah pengaturan level sesi yang MATI secara default dalam kebanyakan kasus.
apakah kita perlu menambahkan pernyataan TRANSAKSI MULAI / AKHIR TRANSAKSI secara eksplisit sehingga kita dapat membatalkan kapan saja?
Dan faktanya, mengingat bahwa Anda ingin dapat menghentikan proses dan memulai kembali, menambahkan transaksi eksplisit (atau mengaktifkan Transaksi Implisit) akan menjadi ide yang buruk karena menghentikan proses mungkin menangkapnya sebelum melakukan hal itu COMMIT
. Dalam hal ini Anda perlu mengeluarkan secara manual COMMIT
(jika Anda berada di SSMS), atau jika Anda menjalankan ini dari pekerjaan SQL Agent, maka Anda tidak memiliki kesempatan itu dan mungkin berakhir dengan transaksi yatim piatu.
Juga, Anda mungkin ingin mengatur @CHUNK_SIZE
ke nomor yang lebih kecil. Eskalasi kunci umumnya terjadi pada 5.000 kunci yang diperoleh pada satu objek. Bergantung pada ukuran baris dan jika melakukan penguncian Baris vs penguncian Halaman, Anda mungkin melewati batas itu. Jika ukuran baris sedemikian rupa sehingga hanya 1 atau 2 baris yang cocok untuk setiap halaman, maka Anda mungkin selalu mengenai hal ini walaupun melakukan kunci Halaman.
Jika tabel dipartisi maka Anda memiliki opsi untuk mengatur LOCK_ESCALATION
opsi (diperkenalkan dalam SQL Server 2008) untuk tabel AUTO
sehingga hanya mengunci partisi dan bukan seluruh tabel saat eskalasi. Atau, untuk tabel mana pun Anda dapat mengatur opsi yang sama DISABLE
, meskipun Anda harus sangat berhati-hati tentang hal itu. Lihat ALTER TABLE untuk detailnya.
Berikut adalah beberapa dokumentasi yang berbicara tentang Kunci Eskalasi dan ambang batas: Kunci Eskalasi (dikatakan berlaku untuk "SQL Server 2008 R2 dan versi yang lebih tinggi"). Dan di sini adalah posting blog yang berhubungan dengan mendeteksi dan memperbaiki eskalasi kunci: Mengunci dalam Microsoft SQL Server (Bagian 12 - Eskalasi Kunci) .
Tidak terkait dengan pertanyaan persis, tetapi terkait dengan permintaan dalam pertanyaan, ada beberapa peningkatan yang dapat dilakukan di sini (atau setidaknya tampaknya seperti itu hanya dengan melihatnya):
Untuk loop Anda, melakukan WHILE (@@ROWCOUNT = @CHUNK_SIZE)
sedikit lebih baik karena jika jumlah baris yang diperbarui pada iterasi terakhir kurang dari jumlah yang diminta untuk UPDATE, maka tidak ada pekerjaan yang harus dilakukan.
Jika deleted
bidang adalah BIT
datatype, maka tidak bahwa nilai yang ditentukan oleh apakah atau tidak deletedDate
adalah 2000-01-01
? Mengapa Anda membutuhkan keduanya?
Jika kedua bidang ini baru dan Anda menambahkannya NULL
sehingga bisa berupa operasi online / non-pemblokiran dan sekarang ingin memperbaruinya ke nilai "default" mereka, maka itu tidak perlu. Dimulai pada SQL Server 2012 (hanya untuk Enterprise Edition), menambahkan NOT NULL
kolom yang memiliki batasan DEFAULT adalah operasi yang tidak memblokir selama nilai DEFAULT adalah konstan. Jadi, jika Anda belum menggunakan bidang, drop dan tambahkan kembali sebagai NOT NULL
dan dengan batasan DEFAULT.
Jika tidak ada proses lain yang memperbarui bidang ini saat Anda melakukan UPDATE ini, maka akan lebih cepat jika Anda mengantri catatan yang ingin Anda perbarui dan kemudian hanya bekerja dari antrian itu. Ada hit kinerja dalam metode saat ini karena Anda harus meminta kembali tabel setiap kali untuk mendapatkan set yang perlu diubah. Sebagai gantinya, Anda bisa melakukan hal berikut ini yang hanya memindai tabel sekali pada kedua bidang tersebut dan kemudian hanya mengeluarkan pernyataan UPDATE yang sangat bertarget. Juga tidak ada penalti untuk menghentikan proses kapan saja dan memulainya nanti karena populasi awal antrian hanya akan menemukan catatan yang tersisa untuk diperbarui.
- Buat tabel sementara (#FullSet) yang hanya memiliki bidang kunci dari indeks berkerumun di dalamnya.
- Buat tabel sementara kedua (#CurrentSet) dari struktur yang sama.
masukkan ke #FullSet via SELECT TOP(n) KeyField1, KeyField2 FROM [huge-table] where deleted is null or deletedDate is null;
Ada TOP(n)
di sana karena ukuran meja. Dengan 100 Juta baris dalam tabel, Anda tidak benar-benar perlu mengisi tabel antrian dengan seluruh set kunci, terutama jika Anda berencana untuk menghentikan proses begitu sering dan memulai kembali nanti. Jadi mungkin diatur n
ke 1 juta dan biarkan berjalan sampai selesai. Anda selalu dapat menjadwalkan ini dalam pekerjaan SQL Agent yang menjalankan set 1 juta (atau mungkin bahkan kurang) dan kemudian menunggu waktu yang dijadwalkan berikutnya untuk mengambil lagi. Anda kemudian dapat menjadwalkan untuk berlari setiap 20 menit sehingga akan ada ruang pernapasan yang dipaksakan antara set n
, tetapi masih akan menyelesaikan seluruh proses tanpa pengawasan. Maka biarkan saja pekerjaan itu dihapus sendiri ketika tidak ada lagi yang harus dilakukan :-).
- dalam satu lingkaran, lakukan:
- Isi bets saat ini melalui sesuatu seperti
DELETE TOP (4995) FROM #FullSet OUTPUT Deleted.KeyField INTO #CurrentSet (KeyField);
IF (@@ROWCOUNT = 0) BREAK;
- Lakukan PEMBARUAN menggunakan sesuatu seperti:
UPDATE ht SET ht.deleted = 0, ht.deletedDate='2000-01-01' FROM [huge-table] ht INNER JOIN #CurrentSet cs ON cs.KeyField = ht.KeyField;
- Kosongkan set saat ini:
TRUNCATE TABLE #CurrentSet;
- Dalam beberapa kasus, membantu menambahkan Indeks Filter untuk membantu
SELECT
umpan yang masuk ke #FullSet
tabel temp. Berikut adalah beberapa pertimbangan terkait dengan menambahkan indeks seperti itu:
- Kondisi WHERE harus cocok dengan kondisi WHERE dari permintaan Anda
WHERE deleted is null or deletedDate is null
- Pada awal proses, sebagian besar baris akan cocok dengan kondisi WHERE Anda, jadi indeks tidak terlalu membantu. Anda mungkin ingin menunggu hingga sekitar 50% sebelum menambahkan ini. Tentu saja, seberapa besar hal itu membantu dan kapan yang terbaik untuk menambahkan indeks bervariasi karena beberapa faktor, jadi ini sedikit trial and error.
- Anda mungkin harus secara manual MEMPERBARUI STATS dan / atau MEMBANGUN KEMBALI indeks untuk tetap up to date karena data dasar berubah cukup sering
- Pastikan untuk mengingat bahwa indeks, sambil membantu
SELECT
, akan menyakiti UPDATE
karena itu adalah objek lain yang harus diperbarui selama operasi itu, maka lebih banyak I / O. Ini berperan baik menggunakan Indeks Filter (yang menyusut saat Anda memperbarui baris karena lebih sedikit baris cocok dengan filter), dan menunggu sebentar untuk menambahkan indeks (jika itu tidak akan sangat membantu di awal, maka tidak ada alasan untuk mengeluarkan I / O tambahan).
UPDATE: Silakan lihat jawaban saya untuk pertanyaan yang terkait dengan pertanyaan ini untuk implementasi penuh dari apa yang disarankan di atas, termasuk mekanisme untuk melacak status dan membatalkan dengan bersih: sql server: memperbarui bidang di atas meja besar dalam potongan kecil: cara mendapatkan kemajuan / status?