Методы ускорения огромного DELETE FROM <table> без предложений

37

Использование SQL Server 2005.

Я выполняю огромное DELETE FROM без предложений where. Это в основном эквивалентно выражению TRUNCATE TABLE - за исключением того, что я не могу использовать TRUNCATE. Проблема в том, что таблица огромна - 10 миллионов строк, и заполнение занимает более часа. Есть ли способ сделать это быстрее без:

  • Использование усечения
  • Отключение или удаление индексов?

Т-журнал уже находится на отдельном диске.

Любые предложения приветствуются!

tuseau
источник
2
Если вы будете много заниматься этим, рассмотрите возможность разделения таблицы
Gaius
1
Вы не можете использовать TRUNCATE, потому что существуют ограничения FK, ссылающиеся на таблицу?
Ник Чаммас

Ответы:

39

Что вы можете сделать, так это групповое удаление:

SELECT 'Starting' --sets @@ROWCOUNT
WHILE @@ROWCOUNT <> 0
    DELETE TOP (xxx) MyTable

Где ххх, скажем, 50000

Модификация этого, если вы хотите удалить очень высокий процент строк ...

SELECT col1, col2, ... INTO #Holdingtable
           FROM MyTable WHERE ..some condition..

SELECT 'Starting' --sets @@ROWCOUNT
WHILE @@ROWCOUNT <> 0
    DELETE TOP (xxx) MyTable WHERE ...

INSERT MyTable (col1, col2, ...)
           SELECT col1, col2, ... FROM #Holdingtable
ГБН
источник
3
@tuseau: при удалении для каждого удаления требуется место в журнале в случае ошибки. Удаление строки 50 КБ занимает меньше ресурсов / места, чем удаление строки 10 м. Конечно, резервные копии журналов по-прежнему работают и т. Д. И занимают много места, но на сервере гораздо легче создавать небольшие партии, чем большие.
ГБН
1
Спасибо, пакетное удаление немного помогает, я думаю, что это лучший вариант.
tuseau
2
@Phil Helmer: если пакетное удаление находится в транзакции, то выигрыш от его использования отсутствует. В противном случае каждая запись в журнале меньше, что просто облегчает загрузку
gbn
1
Еще один комментарий: пакетное удаление очень помогает и требует удаления 20 миллионов строк с 1 часа 42 минут до 3 минут - НО убедитесь, что таблица имеет кластерный индекс! Если это куча, предложение TOP создает сортировку в плане выполнения, которая сводит на нет любое улучшение. Кажется очевидным потом.
Тузо
2
@Noumenon: Это гарантирует, что @@ ROWCOUNT 1
gbn
21

Вы можете использовать предложение TOP, чтобы сделать это легко:

WHILE (1=1)
BEGIN
    DELETE TOP(1000) FROM table
    IF @@ROWCOUNT < 1 BREAK
END
SQLRockstar
источник
В фигурных скобках отформатирован твой код
gbn
@gbn Это на ТАК. здесь это все еще 101 010.
bernd_k
7

Я согласен с предложениями по пакетному удалению в управляемые куски, если вы не можете использовать TRUNCATE, и мне нравится предложение удалить / создать из-за его оригинальности, но мне любопытен следующий комментарий в вашем вопросе:

Это в основном эквивалентно выражению TRUNCATE TABLE - за исключением того, что я не могу использовать TRUNCATE

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

Предполагая, что это так, мне интересно, будет ли созданная хранимая процедура, использующая TRUNCATE TABLE и использующая «EXECUTE AS», приемлемой альтернативой предоставлению прав безопасности, необходимых для прямого усечения таблицы.

Надеемся, что это даст вам необходимую скорость, а также решит проблемы безопасности, которые могут возникнуть в вашей компании при добавлении вашей учетной записи в роль db_ddladmin.

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

Если по какой-то причине это неприемлемое решение и вам необходимо удалить данные из этой таблицы, то это нужно делать один раз в день / час / и т. Д., Я бы попросил создать задание агента SQL для усечения таблицы. в запланированное время каждый день.

Надеюсь это поможет!

Джефф
источник
5

Кроме усечения .. только удаление в пакетах может помочь вам.

Вы можете удалить таблицу и воссоздать ее со всеми ограничениями и индексами. В Management Studio у вас есть возможность сценария удаления и создания таблицы, поэтому это должен быть тривиальный вариант. Но это только в том случае, если вам разрешено выполнять действия DDL, что, на мой взгляд, на самом деле не вариант.

Мэриан
источник
Поскольку приложение предназначено для одновременных операций, изменение структуры (DDL) и использование усечения не являются вариантами ... Я думаю, что пакетное удаление является лучшим из доступных. Спасибо хоть.
tuseau
1

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

Запрос изменен из этого дублирующего вопроса. Кредит @RLF для базы запросов.

CREATE TABLE #DelTest (ID INT IDENTITY, name NVARCHAR(128)); -- Build the test table
INSERT INTO #DelTest (name) SELECT name FROM sys.objects;  -- fill from system DB
SELECT COUNT(*) TableNamesContainingSys FROM #deltest WHERE name LIKE '%sys%'; -- check rowcount
go
DECLARE @HowMany INT;
DECLARE @RowsTouched INT;
DECLARE @TotalRowCount INT;
DECLARE @msg VARCHAR(100);
DECLARE @starttime DATETIME 
DECLARE @currenttime DATETIME 

SET @RowsTouched = 1; -- Needs to be >0 for loop to start
SET @TotalRowCount=0  -- Total rows deleted so far is 0
SET @HowMany = 5;     -- Variable to choose how many rows to delete per loop
SET @starttime=GETDATE()

WHILE @RowsTouched > 0
BEGIN
   DELETE TOP (@HowMany)
   FROM #DelTest 
   WHERE name LIKE '%sys%';

   SET @RowsTouched = @@ROWCOUNT; -- Rows deleted this loop
   SET @TotalRowCount = @TotalRowCount+@RowsTouched; -- Increment Total rows deleted count
   SET @currenttime = GETDATE();
   SELECT @msg='Deleted ' + CONVERT(VARCHAR(9),@TotalRowCount) + ' Records. Runtime so far is '+CONVERT(VARCHAR(30),DATEDIFF(MILLISECOND,@starttime,@currenttime))+' milliseconds.'
   RAISERROR(@msg, 0, 1) WITH NOWAIT;  -- Print message after every loop. Can't use the PRINT function as SQL buffers output in loops.  

END; 
SELECT COUNT(*) TableNamesContainingSys FROM #DelTest WHERE name LIKE '%sys%'; -- Check row count after loop finish
DROP TABLE #DelTest;
Макс хам
источник