Saya tidak mengetahui pertanyaan ini ketika saya menjawab pertanyaan terkait ( Apakah transaksi eksplisit diperlukan dalam loop sementara ini? ), Tetapi demi kelengkapan, saya akan membahas masalah ini di sini karena itu bukan bagian dari saran saya dalam jawaban terkait itu .
Karena saya menyarankan untuk menjadwalkan ini melalui pekerjaan SQL Agent (itu adalah 100 juta baris, setelah semua), saya tidak berpikir bahwa segala bentuk pengiriman pesan status ke klien (yaitu SSMS) akan ideal (meskipun jika itu adalah pernah dibutuhkan untuk proyek lain, maka saya setuju dengan Vladimir bahwa menggunakan RAISERROR('', 10, 1) WITH NOWAIT;
adalah cara untuk pergi).
Dalam kasus khusus ini, saya akan membuat tabel status yang dapat diperbarui per setiap loop dengan jumlah baris yang diperbarui sejauh ini. Dan tidak ada salahnya untuk membuang waktu saat ini untuk memiliki detak jantung pada prosesnya.
Mengingat bahwa Anda ingin dapat membatalkan dan memulai kembali proses, Saya lelah membungkus UPDATE dari tabel utama dengan UPDATE dari tabel status dalam transaksi eksplisit. Namun, jika Anda merasa bahwa tabel status tidak sinkron karena pembatalan, mudah untuk menyegarkan dengan nilai saat ini hanya dengan memperbaruinya secara manual dengan COUNT(*) FROM [huge-table] WHERE deleted IS NOT NULL AND deletedDate IS NOT NULL
.dan ada dua tabel untuk DIPERBARUI (yaitu tabel utama dan tabel status), kita harus menggunakan transaksi eksplisit untuk menjaga kedua tabel tetap sinkron, namun kami tidak ingin mengambil risiko memiliki transaksi yatim jika Anda membatalkan proses di sebuah titik setelah memulai transaksi tetapi belum melakukan itu. Ini harus aman dilakukan selama Anda tidak menghentikan pekerjaan SQL Agent.
Bagaimana Anda bisa menghentikan proses tanpa, um, well, menghentikannya? Dengan memintanya untuk berhenti :-). Ya. Dengan mengirimkan proses "sinyal" (mirip dengan kill -3
di Unix), Anda dapat meminta itu berhenti pada saat yang tepat berikutnya (yaitu ketika tidak ada transaksi aktif!) Dan membuatnya membersihkan semua yang bagus dan rapi.
Bagaimana Anda bisa berkomunikasi dengan proses yang sedang berjalan di sesi lain? Dengan menggunakan mekanisme yang sama yang kami buat untuk mengomunikasikan statusnya saat ini kembali kepada Anda: tabel status. Kita hanya perlu menambahkan kolom yang prosesnya akan memeriksa di awal setiap loop sehingga ia tahu apakah akan melanjutkan atau membatalkan. Dan karena tujuannya adalah untuk menjadwalkan ini sebagai pekerjaan SQL Agent (berjalan setiap 10 atau 20 menit), kita juga harus memeriksa di awal karena tidak ada gunanya mengisi tabel temp dengan 1 juta baris jika prosesnya hanya berjalan untuk keluar sebentar kemudian dan tidak menggunakan data itu.
DECLARE @BatchRows INT = 1000000,
@UpdateRows INT = 4995;
IF (OBJECT_ID(N'dbo.HugeTable_TempStatus') IS NULL)
BEGIN
CREATE TABLE dbo.HugeTable_TempStatus
(
RowsUpdated INT NOT NULL, -- updated by the process
LastUpdatedOn DATETIME NOT NULL, -- updated by the process
PauseProcess BIT NOT NULL -- read by the process
);
INSERT INTO dbo.HugeTable_TempStatus (RowsUpdated, LastUpdatedOn, PauseProcess)
VALUES (0, GETDATE(), 0);
END;
-- First check to see if we should run. If no, don't waste time filling temp table
IF (EXISTS(SELECT * FROM dbo.HugeTable_TempStatus WHERE PauseProcess = 1))
BEGIN
PRINT 'Process is paused. No need to start.';
RETURN;
END;
CREATE TABLE #FullSet (KeyField1 DataType1, KeyField2 DataType2);
CREATE TABLE #CurrentSet (KeyField1 DataType1, KeyField2 DataType2);
INSERT INTO #FullSet (KeyField1, KeyField2)
SELECT TOP (@BatchRows) ht.KeyField1, ht.KeyField2
FROM dbo.HugeTable ht
WHERE ht.deleted IS NULL
OR ht.deletedDate IS NULL
WHILE (1 = 1)
BEGIN
-- Check if process is paused. If yes, just exit cleanly.
IF (EXISTS(SELECT * FROM dbo.HugeTable_TempStatus WHERE PauseProcess = 1))
BEGIN
PRINT 'Process is paused. Exiting.';
BREAK;
END;
-- grab a set of rows to update
DELETE TOP (@UpdateRows)
FROM #FullSet
OUTPUT Deleted.KeyField1, Deleted.KeyField2
INTO #CurrentSet (KeyField1, KeyField2);
IF (@@ROWCOUNT = 0)
BEGIN
RAISERROR(N'All rows have been updated!!', 16, 1);
BREAK;
END;
BEGIN TRY
BEGIN TRAN;
-- do the update of the main table
UPDATE ht
SET ht.deleted = 0,
ht.deletedDate = '2000-01-01'
FROM dbo.HugeTable ht
INNER JOIN #CurrentSet cs
ON cs.KeyField1 = ht.KeyField1
AND cs.KeyField2 = ht.KeyField2;
-- update the current status
UPDATE ts
SET ts.RowsUpdated += @@ROWCOUNT,
ts.LastUpdatedOn = GETDATE()
FROM dbo.HugeTable_TempStatus ts;
COMMIT TRAN;
END TRY
BEGIN CATCH
IF (@@TRANCOUNT > 0)
BEGIN
ROLLBACK TRAN;
END;
THROW; -- raise the error and terminate the process
END CATCH;
-- clear out rows to update for next iteration
TRUNCATE TABLE #CurrentSet;
WAITFOR DELAY '00:00:01'; -- 1 second delay for some breathing room
END;
-- clean up temp tables when testing
-- DROP TABLE #FullSet;
-- DROP TABLE #CurrentSet;
Anda kemudian dapat memeriksa status kapan saja menggunakan kueri berikut:
SELECT sp.[rows] AS [TotalRowsInTable],
ts.RowsUpdated,
(sp.[rows] - ts.RowsUpdated) AS [RowsRemaining],
ts.LastUpdatedOn
FROM sys.partitions sp
CROSS JOIN dbo.HugeTable_TempStatus ts
WHERE sp.[object_id] = OBJECT_ID(N'ResizeTest')
AND sp.[index_id] < 2;
Ingin menghentikan proses, apakah itu berjalan dalam pekerjaan SQL Agent atau bahkan dalam SSMS di komputer orang lain? Lari saja:
UPDATE ht
SET ht.PauseProcess = 1
FROM dbo.HugeTable_TempStatus ts;
Ingin proses untuk dapat memulai kembali? Lari saja:
UPDATE ht
SET ht.PauseProcess = 0
FROM dbo.HugeTable_TempStatus ts;
MEMPERBARUI:
Berikut adalah beberapa hal tambahan untuk dicoba yang dapat meningkatkan kinerja operasi ini. Tidak ada yang dijamin untuk membantu tetapi mungkin layak untuk diuji. Dan dengan 100 juta baris untuk diperbarui, Anda punya banyak waktu / kesempatan untuk menguji beberapa variasi ;-).
- Tambahkan
TOP (@UpdateRows)
ke kueri UPDATE sehingga baris teratas terlihat seperti:
UPDATE TOP (@UpdateRows) ht
Terkadang ini membantu pengoptimal untuk mengetahui berapa banyak baris maks akan terpengaruh sehingga tidak membuang waktu mencari lebih.
Tambahkan KUNCI UTAMA ke #CurrentSet
tabel sementara. Idenya di sini adalah untuk membantu pengoptimal dengan BERGABUNG ke tabel 100 juta baris.
Dan hanya untuk menyatakan agar tidak ambigu, seharusnya tidak ada alasan untuk menambahkan PK ke #FullSet
tabel sementara karena itu hanya tabel antrian sederhana di mana urutannya tidak relevan.
- 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 Filtered Indexed (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).