Мне нужно удалить более 16 миллионов записей из таблицы строк с 221 миллионами, и это происходит очень медленно.
Буду признателен, если вы поделитесь предложениями, чтобы сделать код ниже быстрее:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
DECLARE @BATCHSIZE INT,
@ITERATION INT,
@TOTALROWS INT,
@MSG VARCHAR(500);
SET DEADLOCK_PRIORITY LOW;
SET @BATCHSIZE = 4500;
SET @ITERATION = 0;
SET @TOTALROWS = 0;
BEGIN TRY
BEGIN TRANSACTION;
WHILE @BATCHSIZE > 0
BEGIN
DELETE TOP (@BATCHSIZE) FROM MySourceTable
OUTPUT DELETED.*
INTO MyBackupTable
WHERE NOT EXISTS (
SELECT NULL AS Empty
FROM dbo.vendor AS v
WHERE VendorId = v.Id
);
SET @BATCHSIZE = @@ROWCOUNT;
SET @ITERATION = @ITERATION + 1;
SET @TOTALROWS = @TOTALROWS + @BATCHSIZE;
SET @MSG = CAST(GETDATE() AS VARCHAR) + ' Iteration: ' + CAST(@ITERATION AS VARCHAR) + ' Total deletes:' + CAST(@TOTALROWS AS VARCHAR) + ' Next Batch size:' + CAST(@BATCHSIZE AS VARCHAR);
PRINT @MSG;
COMMIT TRANSACTION;
CHECKPOINT;
END;
END TRY
BEGIN CATCH
IF @@ERROR <> 0
AND @@TRANCOUNT > 0
BEGIN
PRINT 'There is an error occured. The database update failed.';
ROLLBACK TRANSACTION;
END;
END CATCH;
GO
План выполнения (ограничен на 2 итерации)
VendorId
является PK и некластеризованным , где кластерный индекс не используется этим сценарием. Есть 5 других неуникальных некластеризованных индексов.
Задача - «удалить поставщиков, которых нет в другой таблице», и скопировать их в другую таблицу. У меня есть 3 таблицы vendors, SpecialVendors, SpecialVendorBackups
. Попытка удалить то, SpecialVendors
что не существует в Vendors
таблице, и создать резервную копию удаленных записей на случай, если я делаю что-то неправильно, и мне нужно вернуть их через неделю или две.
sql-server
query-performance
delete
cilerler
источник
источник
Ответы:
План выполнения показывает, что он читает строки из некластеризованного индекса в некотором порядке, а затем выполняет поиск для каждой внешней строки, считанной для оценки
NOT EXISTS
Вы удаляете 7,2% таблицы. 16 000 000 строк в 3556 партиях из 4500
Предполагая, что соответствующие строки в конечном итоге распределены по всему индексу, это означает, что он будет удалять примерно 1 строку через каждые 13,8 строки.
Таким образом, итерация 1 прочитает 62 156 строк и выполнит столько операций поиска индекса, прежде чем найдет 4 500 для удаления.
итерация 2 будет читать 57 656 (62 156 - 4500) строк, которые определенно не будут удовлетворять игнорированию любых одновременных обновлений (поскольку они уже были обработаны), а затем еще 62 156 строк, чтобы получить 4500 для удаления.
итерация 3 будет читать (2 * 57 656) + 62 156 строк и так далее, пока, наконец, итерация 3556 не будет читать (3 555 * 57 656) + 62 156 строк и выполнять столько операций поиска.
Таким образом, число поисков индекса, выполненных во всех пакетах, составляет
SUM(1, 2, ..., 3554, 3555) * 57,656 + (3556 * 62156)
Который
((3555 * 3556 / 2) * 57656) + (3556 * 62156)
- или364,652,494,976
Я бы посоветовал вам сначала материализовать строки для удаления во временную таблицу.
И измените «
DELETE
Удалить».WHERE PK IN (SELECT PK FROM #MyTempTable WHERE BatchNumber = @BatchNumber)
Возможно, вам все равно потребуется включитьNOT EXISTS
в самDELETE
запрос, чтобы обслуживать обновления, поскольку временная таблица была заполнена, но это должно быть гораздо более эффективным, поскольку для этого потребуется всего 4500 запросов на пакет.источник
PK
колонка? (Я полагаю, что вы предлагаете мне полностью переместить эти файлы в временную таблицу, но хотели перепроверить)DELETE TOP (@BATCHSIZE) FROM MySourceTable
просто нужноDELETE FROM MySourceTable
также индексировать временную таблицуCREATE TABLE #MyTempTable ( Id BIGINT, BatchNumber BIGINT, PRIMARY KEY(BatchNumber, Id) );
иVendorId
определенно ПК сам по себе? У вас> 221 миллион разных продавцов?План выполнения предполагает, что каждый последующий цикл будет выполнять больше работы, чем предыдущий цикл. Предполагая, что удаляемые строки равномерно распределены по всей таблице, первый цикл должен будет сканировать около 4500 * 221000000/16000000 = 62156 строк, чтобы найти 4500 строк для удаления. Он также будет выполнять такое же количество поисков кластеризованного индекса по
vendor
таблице. Однако второй цикл должен будет прочитать те же строки 62156 - 4500 = 57656, которые вы не удалили в первый раз. Можно ожидать, что второй цикл будет сканировать 120000 строкMySourceTable
и выполнять 120000 операций поиска поvendor
таблице. Количество работы, необходимое для цикла, увеличивается с линейной скоростью. В качестве приближения можно сказать, что в среднем цикле нужно будет прочитать 102516868 строк изMySourceTable
и выполнить 102516868 запросов противvendor
Таблица. Чтобы удалить 16 миллионов строк с размером пакета 4500, ваш код должен выполнить 16000000/4500 = 3556 циклов, поэтому общий объем работы, выполняемой вашим кодом, составляет около 364,5 миллиарда строк, считанных изMySourceTable
и 364,5 миллиарда поисков индекса.Меньшая проблема заключается в том, что вы используете локальную переменную
@BATCHSIZE
в выражении TOP без какой-RECOMPILE
либо другой подсказки. Оптимизатор запросов не будет знать значение этой локальной переменной при создании плана. Предполагается, что он равен 100. В действительности вы удаляете 4500 строк вместо 100, и из-за этого несоответствия вы можете получить менее эффективный план. Оценка низкой мощности при вставке в таблицу также может привести к снижению производительности. SQL Server может выбрать другой внутренний API для вставки, если он считает, что ему нужно вставить 100 строк, а не 4500 строк.Один из вариантов - просто вставить первичные ключи / кластерные ключи строк, которые вы хотите удалить, во временную таблицу. В зависимости от размера ваших ключевых столбцов это может легко поместиться в tempdb. В этом случае вы можете получить минимальное количество записей , что означает, что журнал транзакций не будет взорван. Вы также можете получить минимальное логирование для любой базы данных с моделью восстановления
SIMPLE
. Смотрите ссылку для получения дополнительной информации о требованиях.Если это не вариант, вы должны изменить свой код, чтобы использовать кластерный индекс
MySourceTable
. Ключевым моментом является написание вашего кода, чтобы вы выполняли примерно одинаковое количество работы за цикл. Вы можете сделать это, воспользовавшись индексом, а не просто сканировать таблицу с самого начала каждый раз. Я написал сообщение в блоге, в котором рассматриваются различные методы зацикливания. Примеры в этом посте делают вставки в таблицу, а не удаляют, но вы должны быть в состоянии адаптировать код.В приведенном ниже примере кода я предполагаю, что первичный ключ и ваш кластерный ключ
MySourceTable
. Я написал этот код довольно быстро и не могу его протестировать:Ключевая часть здесь:
Каждый цикл будет читать только 60000 строк
MySourceTable
. Это должно привести к среднему размеру удаления 4500 строк на транзакцию и максимальному размеру удаления 60000 строк на транзакцию. Если вы хотите быть более консервативным с меньшим размером партии, это тоже хорошо. Эти@STARTID
переменные успехи после каждого цикла , так что вы можете избежать чтения и ту же строку более чем один раз из исходной таблицы.источник
На ум приходят две мысли:
Задержка, вероятно, из-за индексации с этим объемом данных. Попробуйте удалить индексы, удалить и перестроить индексы.
Или..
Может быть быстрее скопировать строки, которые вы хотите сохранить, во временную таблицу, удалить таблицу с 16 миллионами строк и переименовать временную таблицу (или скопировать в новый экземпляр исходной таблицы).
источник