Как удалить большие данные таблицы в SQL без журнала?


128

У меня есть большая таблица данных. В этой таблице 10 миллионов записей.

Как лучше всего выполнить этот запрос

   Delete LargeTable where readTime < dateadd(MONTH,-7,GETDATE())

4
:) Боюсь, если вы не хотите написать какой-то ETL, чтобы получить все строки readTime> = dateadd (MONTH, -7, GETDATE ()) в другую таблицу, а затем выпустить таблицу Truncate и вернуть данные с помощью ETL , вы не смогли бы предотвратить запись в лог
TMNT2014

Ведение журнала - это функция «все или ничего» для обеспечения отказоустойчивости транзакций. Буквально не имеет смысла не вести журнал для одной операции, но не для других, иначе журнал будет бесполезен.
Эрик Филипс,

1
Экспортируйте данные, которые вы хотите сохранить, обрежьте таблицу, а затем импортируйте обратно
Bohemian

Другой вариант - использовать переменную таблицы, которая не регистрируется. Следовательно, сохраните ваши данные readTime> = dateadd (MONTH, -7, GETDATE ()) в табличной переменной, а затем усеките исходную таблицу и скопируйте данные из табличной переменной. Однако я бы сохранил резервную копию данных на случай, если что-то пойдет не так и таблица будет непреднамеренно усечена. :) И всегда выполняйте тестовый запуск вашего скрипта в меньшей среде.
TMNT2014

Ответы:


203
  1. Если вы удаляете все строки в этой таблице, самым простым вариантом является усечение таблицы, что-то вроде

    TRUNCATE TABLE LargeTable
    GO
    

    Таблица Truncate просто очистит таблицу, вы не можете использовать предложение WHERE для ограничения удаляемых строк, и никакие триггеры не будут срабатывать.

  2. С другой стороны, если вы удаляете более 80-90 процентов данных, скажем, если у вас всего 11 миллионов строк и вы хотите удалить 10 миллионов, другим способом было бы Вставить эти 1 миллион строк (записи, которые вы хотите сохранить ) в другой промежуточный стол. Обрежьте эту большую таблицу и вставьте обратно эти 1 миллион строк.

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

  4. Последний вариант, о котором я могу подумать, - это изменить вашу базу данных, Recovery Mode to SIMPLEа затем удалять строки меньшими партиями, используя цикл while примерно так ...

    DECLARE @Deleted_Rows INT;
    SET @Deleted_Rows = 1;
    
    
    WHILE (@Deleted_Rows > 0)
      BEGIN
       -- Delete some small number of rows at a time
         DELETE TOP (10000)  LargeTable 
         WHERE readTime < dateadd(MONTH,-7,GETDATE())
    
      SET @Deleted_Rows = @@ROWCOUNT;
    END
    

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


14
Также помните, что если вы усекаете таблицу, у вас не может быть никаких связанных с ней FK.
HLGEM

1
Но как быть уверенным, что вы удаляете 80-90% данных? Предположим, у меня есть только диапазон значений, которые следует удалить. И у меня есть несколько столов. Поэтому я должен проверить каждый из них и вычислить процент, и если он составляет около 30%, я думаю, этот метод не очень эффективен ... Я пытаюсь найти оптимальное решение для неизвестного случая.
Archont

7
@Archont optimal solution for unknown caseэто мечта, не так ли? К сожалению, вы не можете вылечить каждую болезнь одной таблеткой; Я предложил несколько возможных решений для разных сценариев. К сожалению, здесь нет серебряной пули.
M.Ali

5
При выборе варианта 4 следует отметить одну вещь: в зависимости от того, как используется таблица, может быть лучшим вариантом удаление менее 5000 строк за раз, чтобы избежать эскалации блокировки .
Дэниел

Если количество записей для удаления намного больше, чем записей, которые останутся в таблице, я обнаружил, что простой выбор в временную таблицу записей, которые останутся, и удаление исходной таблицы, а переименование временной таблицы выполняется намного быстрее. Учитывая, что вы где-то не используете внешний ключ идентификатора Id.
Владимир Божич

96

@ m-ali ответ правильный, но имейте в виду, что журналы могут сильно вырасти, если вы не фиксируете транзакцию после каждого фрагмента и не выполняете контрольную точку. Вот как я бы сделал это и взял эту статью http://sqlperformance.com/2013/03/io-subsystem/chunk-deletes в качестве справочной с тестами производительности и графиками:

DECLARE @Deleted_Rows INT;
SET @Deleted_Rows = 1;


WHILE (@Deleted_Rows > 0)
  BEGIN

   BEGIN TRANSACTION

   -- Delete some small number of rows at a time
     DELETE TOP (10000)  LargeTable 
     WHERE readTime < dateadd(MONTH,-7,GETDATE())

     SET @Deleted_Rows = @@ROWCOUNT;

   COMMIT TRANSACTION
   CHECKPOINT -- for simple recovery model
END

1
Это должен быть принятый ответ в случае, если доступное дисковое пространство ограничено. Без COMMIT TRANSACTIONи CHECKPOINTжурналы по - прежнему растет. Спасибо, что разъяснили это.
gkoul

+1. Просто обратите внимание, что вы можете сравнить @Deleted_Rowsс 10000, или у вас может получиться бесконечный цикл из-за того, что он бесконечно удаляет небольшие наборы данных. Итак WHILE (@Deleted_Rows = 10000)- как только не будет полной «страницы» данных для удаления, она остановится. В вашей реализации WHILE (@Deleted_Rows > 0)цикл while будет выполняться снова, даже если он удалил только одну строку, и следующее выполнение может также найти строку или две для удаления, что приведет к бесконечному циклу.
NS du Toit,

@NSduToit предложение WHERE рассматривает записи, возраст которых не менее 7 месяцев, поэтому не будет новых записей, удовлетворяющих этому условию, пока вы выполняете удаление.
Франциско Гольденштейн,

@FranciscoGoldenstein Ну, дата , используемая в запросе будет отличаться на каждой итерации , как вы повторно вычислить дату внутри WHILEсамого цикла: dateadd(MONTH,-7,GETDATE()).
NS du Toit,

@FranciscoGoldenstein Также, возможно, для других случаев использования, кроме этого - возможно, новые данные будут добавлены в базовую таблицу, что приведет к новым записям, которые могут быть удалены между разными итерациями WHILEцикла.
NS du Toit,

52

Вы также можете использовать GO +, сколько раз вы хотите выполнить один и тот же запрос.

DELETE TOP (10000)  [TARGETDATABASE].[SCHEMA].[TARGETTABLE] 
WHERE readTime < dateadd(MONTH,-1,GETDATE());
-- how many times you want the query to repeat
GO 100

Мне это нравится, это работает для меня Я случайно вставил одну и ту же строку в таблицу 26 миллионов раз, и мне нужно было удалить все ее вхождения, которые в одном единственном операторе удаления исчерпали память на сервере, так что это отличный вопрос , остановит ли он средний цикл, если в нем закончатся строки для удаления?
ScottC

2
@ScottC, это не цикл, он просто повторяет запрос (например, пакетный), и если у вас заканчиваются строки, он не может ничего удалить. Но это не остановит. вы получите что-то вроде (0 затронутых строк), если в нем закончатся строки, которые вы удаляете.
Bunkerbuster

ах, да, я обнаружил, что примерно через 5 минут после того, как я разместил свой вопрос, так как мое удаление закончилось, спасибо, это было очень полезно!
ScottC

1
С какого MS SQL Server GO xxдолжен работать этот синтаксис ? Я получаю сообщение об ошибке «Не удалось найти хранимую процедуру» . Однако без GOкоманды он работает нормально.
Abel

3
Хм, похоже, я могу его выполнить, и он действительно запускается несколько раз, но в MS SQL Mgt Studio он показывает красную фигурную линию с упомянутой ошибкой (но тогда работает F5-run)
Абель

11

@Francisco Goldenstein, небольшая поправка. COMMIT необходимо использовать после установки переменной, иначе WHILE будет выполнен только один раз:

DECLARE @Deleted_Rows INT;
SET @Deleted_Rows = 1;

WHILE (@Deleted_Rows > 0)
BEGIN
    BEGIN TRANSACTION

    -- Delete some small number of rows at a time
    DELETE TOP (10000)  LargeTable 
    WHERE readTime < dateadd(MONTH,-7,GETDATE())

    SET @Deleted_Rows = @@ROWCOUNT;

    COMMIT TRANSACTION
    CHECKPOINT -- for simple recovery model

END

10

Эта вариация M.Ali мне подходит. Некоторые удаляются, журнал очищается и повторяется. Я смотрю, как журнал растет, опускается и начинается сначала.

DECLARE @Deleted_Rows INT;
SET @Deleted_Rows = 1;
WHILE (@Deleted_Rows > 0)
  BEGIN
   -- Delete some small number of rows at a time
    delete top (100000) from InstallLog where DateTime between '2014-12-01' and '2015-02-01'
    SET @Deleted_Rows = @@ROWCOUNT;
    dbcc shrinkfile (MobiControlDB_log,0,truncateonly);
END

Это было очень полезно! Я изменил его, чтобы параметризовать # of rowsудаление за раз, а также WHEREпредложение. Работает как шарм!
Шива

7

Если вы хотите (и можете) реализовать секционирование, это эффективный метод для удаления больших объемов данных с небольшими затратами времени выполнения. Однако это не рентабельно для разового упражнения.


4

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

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

  1. Получить минимальное значение первичного ключа большой таблицы, где readTime <dateadd (MONTH, -7, GETDATE ()). (Добавьте индекс в readTime, если он еще не присутствует, этот индекс все равно будет удален вместе с таблицей на шаге 3.). Сохраним его в переменной min_primary.

  2. Вставьте все строки с первичным ключом> min_primary в промежуточную таблицу (таблица памяти, если количество строк невелико).

  3. Бросьте большой стол.

  4. Восстановите таблицу. Скопируйте все строки из промежуточной таблицы в основную.

  5. Отбросьте промежуточный стол.


3

Вы можете удалять небольшие партии, используя цикл while, примерно так:

DELETE TOP (10000)  LargeTable 
WHERE readTime < dateadd(MONTH,-7,GETDATE())
WHILE @@ROWCOUNT > 0
BEGIN
    DELETE TOP (10000)  LargeTable 
    WHERE readTime < dateadd(MONTH,-7,GETDATE())
END

2

Другое использование:

SET ROWCOUNT 1000 -- Buffer

DECLARE @DATE AS DATETIME = dateadd(MONTH,-7,GETDATE())

DELETE LargeTable  WHERE readTime < @DATE
WHILE @@ROWCOUNT > 0
BEGIN
   DELETE LargeTable  WHERE readTime < @DATE
END
SET ROWCOUNT 0

По желанию;

Если журнал транзакций включен, отключите журналы транзакций.

ALTER DATABASE dbname SET RECOVERY SIMPLE;

2

Более короткий синтаксис

select 1
WHILE (@@ROWCOUNT > 0)
BEGIN
  DELETE TOP (10000) LargeTable 
  WHERE readTime < dateadd(MONTH,-7,GETDATE())
END

1

Если вы используете SQL Server 2016 или выше, и если в вашей таблице есть разделы, созданные на основе столбца, который вы пытаетесь удалить (например, столбец Timestamp), вы можете использовать эту новую команду для удаления данных по разделам.

ОБРЕЗАТЬ ТАБЛИЦУ С (РАЗДЕЛЫ ({|} [, ... n]))

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

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

Для получения дополнительных сведений см. Ссылки ниже: https://docs.microsoft.com/en-us/sql/t-sql/statements/truncate-table-transact-sql?view=sql-server-2017

Таблица усечения SQL Server 2016 с разделами

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

:connect <<ServerName>>
use <<DatabaseName>>

SET NOCOUNT ON;
DECLARE @Deleted_Rows INT;
DECLARE @loopnum INT;
DECLARE @msg varchar(100);
DECLARE @FlagDate datetime;
SET @FlagDate =  getdate() - 31;
SET @Deleted_Rows = 1;
SET @loopnum = 1;

/*while (getdate() < convert(datetime,'2018-11-08 14:00:00.000',120))
BEGIN
    RAISERROR( 'WAIT for START' ,0,1) WITH NOWAIT   
    WAITFOR DELAY '00:10:00'
END*/
RAISERROR( 'STARTING PURGE' ,0,1) WITH NOWAIT   

WHILE (1=1)
BEGIN
    WHILE (@Deleted_Rows > 0 AND (datepart(hh, getdate() ) >= 12 AND datepart(hh, getdate() ) <= 20)) -- (getdate() < convert(datetime,'2018-11-08 19:00:00.000',120) )
      BEGIN
       -- Delete some small number of rows at a time
         DELETE TOP (500000)  dbo.<<table_name>>
         WHERE timestamp_column < convert(datetime, @FlagDate,102)
         SET @Deleted_Rows = @@ROWCOUNT;
         WAITFOR DELAY '00:00:01'
         select @msg = 'ROWCOUNT' + convert(varchar,@Deleted_Rows);
         set @loopnum = @loopnum + 1
         if @loopnum > 1000
             begin 
                 begin try
                        DBCC SHRINKFILE (N'<<databasename>>_log' , 0, TRUNCATEONLY)
                        RAISERROR( @msg ,0,1) WITH NOWAIT
                 end try
                 begin catch
                     RAISERROR( 'DBCC SHRINK' ,0,1) WITH NOWAIT  
                 end catch
                 set @loopnum = 1
             end
        END
WAITFOR DELAY '00:10:00'
END 
select getdate()

0

Если я говорю без цикла, я могу использовать GOTOоператор для удаления большого количества записей с помощью sql-сервера. экс.

 IsRepeat:
    DELETE TOP (10000)
    FROM <TableName>
    IF @@ROWCOUNT > 0
         GOTO IsRepeat

таким образом вы можете удалить большой объем данных с меньшим размером удаления.

дайте мне знать, если потребуется дополнительная информация.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.