Як видалити великі дані таблиці в SQL без журналу?


128

У мене велика таблиця даних. У цій таблиці 10 мільйонів записів.

Який найкращий спосіб для цього запиту

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

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

Ведення журналу - це функція, яка має все або нічого, або мати стійкі транзакції. Буквально не має сенсу мати журнал для деяких операцій, але не для інших, інакше журнал марний.
— Ерік Філіпс

1
Експортуйте дані, які ви хочете зберегти, усікайте таблицю, а потім імпортуйте назад
— Bohemian

Іншим варіантом було б використання змінної таблиці, яка не реєструється. Отже, зберігайте свої дані readTime> = dateadd (MONTH, -7, GETDATE ()) у змінній таблиці, а потім усікайте оригінальну таблицю та копіюйте дані із змінної таблиці. Однак я б продовжував зберігати резервну копію даних, якщо щось пішло не так, і таблиця ненароком обрізається. :) І завжди робіть тестовий запуск вашого сценарію в меншому середовищі.
— TMNT2014

Відповіді:


203
  1. Якщо ви видаляєте всі рядки з цієї таблиці, найпростіший варіант - урізати таблицю, щось подібне

    TRUNCATE TABLE LargeTable
    GO

    Обрізана таблиця просто спорожнить таблицю, ви не можете використовувати пункт WHERE для обмеження видалення рядків і жодні тригери не будуть запускатися.

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

  3. Або якщо на дозволи / перегляди чи інші об'єкти, на яких розміщена ця велика таблиця, як їх основна таблиця, не постраждає, відкинувши цю таблицю, ви можете отримати ці відносно невеликі кількості рядків в іншу таблицю, скинути цю таблицю та створити іншу таблицю з тією ж схемою та імпортувати ці ряди назад у цю колишню велику таблицю.

  4. Останній варіант, про який я можу придумати, - це змінити базу даних, Recovery Mode to SIMPLEа потім видалити рядки меншими партіями, використовуючи цикл у той час як щось подібне ..

    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%, я думаю, що цей метод не дуже ефективний ... Я намагаюся знайти оптимальне рішення для невідомого випадку.
— Архонт

7
@ Архонт optimal solution for unknown case, це мрія, чи не так? На жаль, не можна вилікувати кожну хворобу жодною таблеткою; Я запропонував кілька можливих рішень для різних сценаріїв. На жаль, тут немає жодної кулі.
— М.Алі

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

Якщо кількість записів для видалення набагато більша, ніж записів, які залишатимуться в таблиці, я виявив, що простий вибір у темп-таблиці записів, які залишатимуться та випадати вихідну таблицю та перейменувати таблицю темпів, буде набагато швидше. З огляду на те, що ви десь не використовуєте іноземний ключ Id
— Володимир Бозич

96

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

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 мільйонів разів і мені потрібно було видалити всі його події, які в одному одному операторі видалення втратили пам’ять на сервері, тож це чудово одне питання , чи зупиниться середній цикл, якщо у нього не буде рядків для видалення?
— Скотт

2
@ScottC, це не цикл, він просто повторює запит (схожий на пакет), і якщо ви закінчите рядки, він нічого не може видалити. Але це не зупиниться. ви отримаєте щось на зразок (0 рядків), якщо це закінчиться із рядків, які ви видалите.
— Бункербастер

ах, так, я виявив, що приблизно через 5 хвилин після того, як я опублікував своє запитання, оскільки видалення закінчилося, дякую, це було дуже корисно!
— Скотт

1
З якого MS SQL Server цей синтаксис GO xxповинен працювати? Я отримую помилку "Не вдалося знайти збережену процедуру" "" . Без GOкоманди він працює добре.
— Абель

3
Гм, здається, я можу його виконати, і він працює справді кілька разів, але в MS SQL Mgt Studio він показує червону фігурну лінію зі згаданою помилкою (але F5-запуск працює тоді)
— Abel,

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

Ця варіація творів М.Алі працює для мене чудово. Він видаляє деякі, очищає журнал і повторює. Я спостерігаю, як журнал росте, опускається і починається спочатку.

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-сервер 2016 або новішої версії, і якщо у вашій таблиці є розділи, створені на основі стовпця, який ви намагаєтесь видалити (наприклад, стовпець Timestamp), ви можете використовувати цю нову команду для видалення даних розділами.

ТРУНКАЦІЙНА ТАБЛИЦА З (РОЗДІЛИ ({|} [, ... n]))

Це видалить дані лише у вибраних розділах і має бути найефективнішим способом видалення даних з частини таблиці, оскільки це не створюватиме журнали транзакцій і буде виконано так само швидко, як звичайне скорочення, але без видалення всіх даних зі столу.

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

Більш детальну інформацію можна знайти за посиланнями нижче: https://docs.microsoft.com/en-us/sql/t-sql/statements/truncate-table-transact-sql?view=sql-server-2017

SQL-сервер 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.