Я не знал об этом вопросе, когда отвечал на связанный вопрос ( нужны ли явные транзакции в этом цикле while? ), Но для полноты картины я рассмотрю этот вопрос здесь, поскольку он не был частью моего предложения в этом связанном ответе. ,
Поскольку я предлагаю запланировать это с помощью задания агента SQL (в конце концов, это 100 миллионов строк), я не думаю, что любая форма отправки сообщений о состоянии клиенту (например, SSMS) будет идеальной (хотя, если это так Если когда-нибудь понадобятся другие проекты, то я согласен с Владимиром, что использование RAISERROR('', 10, 1) WITH NOWAIT;
- это путь).
В этом конкретном случае я бы создал таблицу состояния, которая может обновляться для каждого цикла с количеством обновленных строк. И это не мешает бросить в текущее время, чтобы иметь сердцебиение на процессе.
Учитывая, что вы хотите иметь возможность отменить и перезапустить процесс, Я устал от обертывания UPDATE основной таблицы с UPDATE таблицы состояния в явной транзакции. Однако, если вы чувствуете, что таблица состояния не синхронизирована из-за отмены, легко обновить текущее значение, просто обновив его вручную с помощью COUNT(*) FROM [huge-table] WHERE deleted IS NOT NULL AND deletedDate IS NOT NULL
.и есть две таблицы для ОБНОВЛЕНИЯ (то есть основная таблица и таблица состояния), мы должны использовать явную транзакцию, чтобы синхронизировать эти две таблицы, но мы не хотим рисковать потерянной транзакцией, если вы отмените процесс в точка после того, как он начал транзакцию, но не совершил ее. Это должно быть безопасно, если вы не остановите задание агента SQL.
Как вы можете остановить процесс, не прекращая его? Прося это остановить :-). Ага. Отправив процессу «сигнал» (аналогично kill -3
Unix), вы можете запросить его остановку в следующий удобный момент (т. Е. Когда нет активной транзакции!) И заставить его очистить себя от всего приятного и аккуратного.
Как вы можете общаться с запущенным процессом в другой сессии? Используя тот же механизм, который мы создали для него, чтобы сообщить вам его текущий статус: таблицу состояния. Нам просто нужно добавить столбец, который процесс будет проверять в начале каждого цикла, чтобы он знал, продолжить или прервать. А поскольку цель состоит в том, чтобы запланировать это как задание агента SQL (запускать каждые 10 или 20 минут), мы также должны проверить в самом начале, так как нет смысла заполнять временную таблицу 1 миллионом строк, если процесс только идет выйти через мгновение и не использовать эти данные.
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;
Затем вы можете проверить статус в любое время, используя следующий запрос:
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;
Хотите приостановить процесс, выполняется ли он в задании агента SQL или даже в SSMS на чужом компьютере? Просто беги:
UPDATE ht
SET ht.PauseProcess = 1
FROM dbo.HugeTable_TempStatus ts;
Хотите, чтобы процесс мог начать заново? Просто беги:
UPDATE ht
SET ht.PauseProcess = 0
FROM dbo.HugeTable_TempStatus ts;
ОБНОВИТЬ:
Вот некоторые дополнительные действия, которые могут улучшить производительность этой операции. Никто не гарантированно поможет, но, вероятно, стоит попробовать. А благодаря обновлению 100 миллионов строк у вас будет достаточно времени / возможностей для тестирования некоторых вариантов ;-).
- Добавьте
TOP (@UpdateRows)
к запросу UPDATE, чтобы верхняя строка выглядела следующим образом:
UPDATE TOP (@UpdateRows) ht
Иногда это помогает оптимизатору узнать, на какое число строк будет оказано влияние, чтобы не тратить время на поиск большего количества.
Добавьте ПЕРВИЧНЫЙ КЛЮЧ во #CurrentSet
временную таблицу. Идея состоит в том, чтобы помочь оптимизатору присоединиться к таблице 100 миллионов строк.
И просто чтобы это было заявлено, чтобы не быть двусмысленным, не должно быть никаких причин добавлять PK во #FullSet
временную таблицу, поскольку это просто простая таблица очередей, где порядок не имеет значения.
- В некоторых случаях это помогает добавить фильтрованный индекс, чтобы помочь тому,
SELECT
что подается во #FullSet
временную таблицу. Вот некоторые соображения, связанные с добавлением такого индекса:
- Условие WHERE должно соответствовать условию WHERE вашего запроса, поэтому
WHERE deleted is null or deletedDate is null
- В начале процесса большинство строк будут соответствовать вашему условию WHERE, поэтому индекс не так уж полезен. Возможно, вы захотите подождать где-нибудь около отметки 50%, прежде чем добавлять это. Конечно, насколько это помогает и когда лучше всего добавлять индекс, зависит от нескольких факторов, так что это немного проб и ошибок.
- Возможно, вам придется вручную обновить STATS и / или перестроить индекс, чтобы поддерживать его в актуальном состоянии, поскольку базовые данные меняются довольно часто
- Обязательно имейте в виду, что индекс, хотя и помогает
SELECT
, повредит, UPDATE
поскольку это еще один объект, который должен быть обновлен во время этой операции, а следовательно, больше операций ввода-вывода. Это влияет как на использование фильтрованного индекса (который уменьшается при обновлении строк, так как фильтр соответствует меньшему количеству строк), так и на ожидание добавления индекса (если вначале это не очень полезно, то нет причин для этого дополнительный ввод / вывод).
WAITFOR DELAY
до половины секунды или около того, но это компромисс с параллелизмом и, возможно, сколько пересылается через доставку журналов.Отвечая на вторую часть: как напечатать какой-нибудь вывод во время цикла.
У меня есть несколько длительных процедур обслуживания, которые иногда должен запускать sys admin.
Я запустил их из SSMS и также заметил, что
PRINT
оператор отображается в SSMS только после завершения всей процедуры.Итак, я использую
RAISERROR
с низкой степенью серьезности:Я использую SQL Server 2008 Standard и SSMS 2012 (11.0.3128.0). Вот полный рабочий пример для запуска в SSMS:
Когда я закомментирую
RAISERROR
и оставлю толькоPRINT
сообщения на вкладке «Сообщения» в SSMS, они появятся только после завершения всего пакета, через 6 секунд.Когда я закомментирую
PRINT
и используюRAISERROR
сообщения на вкладке «Сообщения» в SSMS, они появляются без ожидания в течение 6 секунд, но по мере прохождения цикла.Интересно, что когда я использую оба
RAISERROR
иPRINT
, я вижу оба сообщения. Сначала приходит сообщение сначалаRAISERROR
, затем задержка на 2 секунды, затем сначалаPRINT
и второеRAISERROR
, и так далее.В других случаях я использую отдельную выделенную
log
таблицу и просто вставляю в таблицу строку с некоторой информацией, описывающей текущее состояние и временную метку длительного процесса.Пока идет долгий процесс, я периодически
SELECT
изlog
таблицы вижу, что происходит.Это, очевидно, имеет определенные накладные расходы, но оставляет журнал (или историю журналов), который я могу просмотреть в своем собственном темпе позже.
источник
Вы можете отслеживать это из другого соединения с помощью чего-то вроде:
чтобы увидеть, сколько еще осталось сделать. Это может быть полезно, если приложение вызывает процесс, а не запускает его вручную в SSMS или подобном, и ему необходимо показать прогресс: запустить основной процесс асинхронно (или в другом потоке), а затем выполнить цикл с вызовом «сколько осталось» msgstr "проверять каждый раз, пока асинхронный вызов (или поток) не завершится.
Установка максимально низкого уровня изоляции означает, что он должен вернуться в разумные сроки, не застревая за основной транзакцией из-за проблем с блокировкой. Это может означать, что возвращаемое значение, конечно, немного неточно, но как простой индикатор прогресса это не должно иметь никакого значения.
источник