Як видалити великі дані таблиці в 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.