Как эффективно удалять строки, не используя таблицу Truncate в таблице 500 000 строк

Скажем, у нас есть таблица Sales с 30 столбцами и 500 000 строк. Я хотел бы удалить 400 000 в таблице (те, где "toDelete='1'").

Но у меня есть несколько ограничений:

  • таблица читается/записывается "часто", и мне не нужно долгое "удалять" длительное время и слишком долго блокировать таблицу.
  • Мне нужно пропустить журнал транзакций (например, с помощью TRUNCATE), но при выполнении "DELETE ... WHERE..." (мне нужно поставить условие), но не нашел способа сделать это...

Любые советы были бы полезны для преобразования

DELETE FROM Sales WHERE toDelete='1'

для чего-то более секционированного и, возможно, журнала транзакций.

Ответ 1

Вызов DELETE FROM TableName сделает все удаление в одной крупной транзакции. Это дорого.

Вот еще один вариант, который будет удалять строки пакетами:

deleteMore:
DELETE TOP(10000) Sales WHERE toDelete='1'
IF @@ROWCOUNT != 0
    goto deleteMore

Ответ 2

Что вы хотите - пакетная обработка.

While (select Count(*) from sales where toDelete =1) >0
BEGIN
Delete from sales where SalesID in
(select top 1000 salesId from sales where toDelete = 1)
END

Конечно, вы можете поэкспериментировать, что лучше всего использовать для партии, я использовал от 500 до 50000 в зависимости от таблицы. Если вы используете каскадное удаление, вам, вероятно, понадобится меньшее число, так как у вас есть эти дочерние записи для удаления.

Ответ 3

Один из способов, которым я должен был это сделать в прошлом, - иметь хранимую процедуру или script, которая удаляет n записей. Повторяйте до конца.

DELETE TOP 1000 FROM Sales WHERE toDelete='1'

Ответ 4

Вам следует попытаться дать подсказку ROWLOCK, чтобы она не блокировала всю таблицу. Однако, если вы удалите много строк, произойдет эскалация блокировки.

Кроме того, убедитесь, что в столбце toDelete есть некластеризованный отфильтрованный индекс (только для 1 значения). Если возможно, сделайте бит столбцом, а не varchar (или тем, что он сейчас).

DELETE FROM Sales WITH(ROWLOCK) WHERE toDelete='1'

В конечном счете, вы можете попробовать выполнить итерацию по таблице и удалить в кусках.

Обновление

Так как в то время как петли и удаленные фрагменты являются новыми розовыми здесь, я тоже вложу свою версию (в сочетании с моим предыдущим ответом):

SET ROWCOUNT 100
DELETE FROM Sales WITH(ROWLOCK) WHERE toDelete='1'

WHILE @@rowcount > 0
BEGIN
  SET ROWCOUNT 100
  DELETE FROM Sales WITH(ROWLOCK) WHERE toDelete='1'  
END

Ответ 5

Мой собственный подход к этой функции будет следующим. Таким образом, нет повторяющегося кода, и вы можете управлять размером вашего блока.

DECLARE @DeleteChunk INT = 10000
DECLARE @rowcount INT = 1

WHILE @rowcount > 0
BEGIN

  DELETE TOP (@DeleteChunk) FROM Sales WITH(ROWLOCK)

  SELECT @rowcount = @@RowCount
END

Ответ 6

Я оставлю свой ответ здесь, так как я смог протестировать различные подходы для массового удаления и обновления (мне пришлось обновить и затем удалить 125 миллионов строк, сервер имеет 16 ГБ ОЗУ, Xeon E5-2680 @2,7 ГГц, SQL Server 2012).

TL; DR: всегда обновлять/удалять по первичному ключу, а не по каким-либо другим условиям. Если вы не можете использовать PK напрямую, создайте временную таблицу и заполните ее значениями PK и обновите/удалите вашу таблицу, используя эту таблицу. Используйте индексы для этого.

Я начал с решения сверху (@Kevin Aenmey), но этот подход оказался неуместным, так как моя база данных работала и обрабатывает пару сотен транзакций в секунду, и была некоторая блокировка (был индекс для всех там поля из условия, использование WITH(ROWLOCK) ничего не изменило).

Итак, я добавил оператор WAITFOR, который позволял базе данных обрабатывать другие транзакции.

deleteMore:
WAITFOR DELAY '00:00:01'
DELETE TOP(1000) FROM MyTable WHERE Column1 = @Criteria1 AND Column2 = @Criteria2 AND Column3 = @Criteria3
IF @@ROWCOUNT != 0
    goto deleteMore

Этот подход смог обработать ~ 1.6 млн. Строк/час для обновления и ~ 0,2 млн. Строк/час для удаления.

Переход к временным таблицам изменил многое.

deleteMore:
SELECT TOP 10000 Id /* Id is the PK */
  INTO #Temp 
  FROM MyTable WHERE Column1 = @Criteria1 AND Column2 = @Criteria2 AND Column3 = @Criteria3 

DELETE MT
  FROM MyTable MT
  JOIN #Temp T ON T.Id = MT.Id 

/* you can use IN operator, it doesn't change anything
 DELETE FROM MyTable WHERE Id IN (SELECT Id FROM #Temp)

 */
IF @@ROWCOUNT > 0 BEGIN
    DROP TABLE #Temp
    WAITFOR DELAY '00:00:01'
    goto deleteMore
END ELSE BEGIN
    DROP TABLE #Temp
    PRINT 'This is the end, my friend'
END

Это решение обрабатывало ~ 25 миллионов строк/час для обновления (в 15 раз быстрее) и ~ 2,2 миллиона строк/час для удаления (в 11 раз быстрее).

Ответ 7

Я использовал ниже, чтобы удалить около 50 миллионов записей -

BEGIN TRANSACTION     
     DeleteOperation:
     DELETE TOP (BatchSize)
     FROM  [database_name].[database_schema].[database_table] 

     IF @@ROWCOUNT > 0
     GOTO DeleteOperation
COMMIT TRANSACTION

Обратите внимание, что сохранение BatchSize < 5000 дешевле на ресурсах.

Ответ 8

Как я полагаю, лучший способ удалить огромное количество записей - удалить его с помощью Primary Key. (Что такое Primary Key см. Здесь)

Итак, вам нужно сгенерировать tsql script, который содержит весь список удаляемых строк и после этого выполнить этот script.

Например, код ниже должен сгенерировать этот файл

GO
SET NOCOUNT ON

SELECT   'DELETE FROM  DATA_ACTION WHERE ID = ' + CAST(ID AS VARCHAR(50)) + ';' + CHAR(13) + CHAR(10) + 'GO'
FROM    DATA_ACTION
WHERE  YEAR(AtTime) = 2014

В выходном файле будут записи типа

DELETE FROM  DATA_ACTION WHERE ID = 123;
GO
DELETE FROM  DATA_ACTION WHERE ID = 124;
GO
DELETE FROM  DATA_ACTION WHERE ID = 125;
GO

И теперь вам нужно использовать утилиту SQLCMD, чтобы выполнить этот script.

sqlcmd -S [Instance Name] -E -d [Database] -i [Script]

Этот подход можно найти здесь https://www.mssqltips.com/sqlservertip/3566/deleting-historical-data-from-a-large-highly-concurrent-sql-server-database-table/