Apakah transaksi eksplisit diperlukan dalam loop sementara ini?

11

SQL Server 2014:

Kami memiliki tabel (100 juta baris) yang sangat besar, dan kami perlu memperbarui beberapa bidang di atasnya.

Untuk pengiriman log, dll, kami juga, tentu saja, ingin menyimpannya untuk transaksi ukuran gigitan.

Jika kita membiarkan hal-hal di bawah ini berjalan sebentar, dan kemudian membatalkan / mengakhiri kueri, apakah pekerjaan yang dilakukan sejauh ini akan dilakukan, atau apakah kita perlu menambahkan pernyataan TRANSAKSI MULAI / TRANSAKSI AKHIR secara eksplisit sehingga kita dapat membatalkan kapan saja?

DECLARE @CHUNK_SIZE int
SET @CHUNK_SIZE = 10000

UPDATE TOP(@CHUNK_SIZE) [huge-table] set deleted = 0, deletedDate = '2000-01-01'
where deleted is null or deletedDate is null

WHILE @@ROWCOUNT > 0
BEGIN
    UPDATE TOP(@CHUNK_SIZE) [huge-table] set deleted = 0, deletedDate = '2000-01-01'
    where deleted is null or deletedDate is null
END
Jonesome Reinstate Monica
sumber

Jawaban:

13

Pernyataan individu - DML, DDL, dll - adalah transaksi sendiri. Jadi ya, setelah setiap iterasi dari loop (secara teknis: setelah setiap pernyataan), apa pun UPDATEpernyataan yang diubah telah dikomit secara otomatis.

Tentu saja, selalu ada pengecualian, bukan? Dimungkinkan untuk mengaktifkan Transaksi Implisit melalui SET IMPLICIT_TRANSACTIONS , dalam hal ini UPDATEpernyataan pertama akan memulai transaksi yang Anda harus COMMITatau ROLLBACKpada 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_SIZEke 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_ESCALATIONopsi (diperkenalkan dalam SQL Server 2008) untuk tabel AUTOsehingga 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):

  1. 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.

  2. Jika deletedbidang adalah BITdatatype, maka tidak bahwa nilai yang ditentukan oleh apakah atau tidak deletedDateadalah 2000-01-01? Mengapa Anda membutuhkan keduanya?

  3. Jika kedua bidang ini baru dan Anda menambahkannya NULLsehingga 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 NULLkolom 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 NULLdan dengan batasan DEFAULT.

  4. 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.

    1. Buat tabel sementara (#FullSet) yang hanya memiliki bidang kunci dari indeks berkerumun di dalamnya.
    2. Buat tabel sementara kedua (#CurrentSet) dari struktur yang sama.
    3. 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 nke 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 :-).

    4. dalam satu lingkaran, lakukan:
      1. Isi bets saat ini melalui sesuatu seperti DELETE TOP (4995) FROM #FullSet OUTPUT Deleted.KeyField INTO #CurrentSet (KeyField);
      2. IF (@@ROWCOUNT = 0) BREAK;
      3. 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;
      4. Kosongkan set saat ini: TRUNCATE TABLE #CurrentSet;
  5. Dalam beberapa kasus, membantu menambahkan Indeks Filter untuk membantu SELECTumpan yang masuk ke #FullSettabel temp. Berikut adalah beberapa pertimbangan terkait dengan menambahkan indeks seperti itu:
    1. Kondisi WHERE harus cocok dengan kondisi WHERE dari permintaan Anda WHERE deleted is null or deletedDate is null
    2. 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.
    3. Anda mungkin harus secara manual MEMPERBARUI STATS dan / atau MEMBANGUN KEMBALI indeks untuk tetap up to date karena data dasar berubah cukup sering
    4. Pastikan untuk mengingat bahwa indeks, sambil membantu SELECT, akan menyakiti UPDATEkarena 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?

Solomon Rutzky
sumber
Saran Anda dalam # 4 mungkin lebih cepat dalam beberapa kasus, tetapi sepertinya menambah kompleksitas kode. Saya lebih suka memulai dari yang sederhana, dan kemudian jika itu tidak memenuhi kebutuhan Anda, pertimbangkan alternatif.
Bacon Bits
@BaconBits Setuju untuk memulai yang sederhana. Agar adil, saran-saran ini tidak dimaksudkan untuk diterapkan pada semua skenario. Pertanyaannya adalah tentang berurusan dengan tabel (100 juta + baris) yang sangat besar.
Solomon Rutzky