Перемещение данных SQL Server ограниченными порциями (1000 строк)

Я пишу процесс, который архивирует строки из таблицы SQL Server на основе столбца datetime. Я хочу переместить все строки с датой перед X, но проблема в том, что для каждой даты есть миллионы строк, поэтому выполнение BEGIN TRANSACTION ... INSERT ... DELETE ... COMMIT для каждой даты занимает слишком много времени , и блокирует базу данных для других пользователей.

Есть ли способ сделать это небольшими порциями? Может быть, используя ROWCOUNT или что-то в этом роде?

Изначально я думал примерно так:

SET ROWCOUNT 1000

DECLARE @RowsLeft DATETIME
DECLARE @ArchiveDate DATETIME

SET @ROWSLEFT = (SELECT TOP 1 dtcol FROM Events WHERE dtcol <= @ArchiveDate)

WHILE @ROWSLEFT IS NOT NULL
BEGIN

    INSERT INTO EventsBackups
    SELECT top 1000 * FROM Events

    DELETE Events

    SET @ROWSLEFT = (SELECT TOP 1 dtcol FROM Events WHERE dtcol <= @ArchiveDate)

END

Но потом я понял, что не могу гарантировать, что удаляемые мной строки - это те, которые я только что скопировал. Или я могу ...?

ОБНОВЛЕНИЕ: Я рассмотрел еще один вариант - добавить шаг:

  1. ВЫБЕРИТЕ ТОП 1000 строк, которые соответствуют моим критериям даты, во временную таблицу
  2. Начать транзакцию
  3. Вставить из временной таблицы в архивную таблицу
  4. Удалить из исходной таблицы, присоединяясь к временной таблице по каждому столбцу
  5. Подтвердить транзакцию
  6. Повторяйте 1–5, пока не останется строк, соответствующих критериям даты.

Есть ли у кого-нибудь представление о том, как можно сравнить расходы на эту серию с некоторыми другими вариантами, обсуждаемыми ниже?

ПОДРОБНЕЕ: я использую SQL 2005, так как кто-то спросил.


person SqlRyan    schedule 14.05.2009    source источник
comment
предложения OUTPUT и INTO - ваш друг, посмотрите их или посмотрите мой ответ ...   -  person KM.    schedule 14.05.2009


Ответы (8)


Просто ВСТАВЬТЕ результат УДАЛИТЬ:

WHILE 1=1
BEGIN

    WITH EventsTop1000 AS (
    SELECT TOP 1000 * 
        FROM Events
      WHERE <yourconditionofchoice>)
    DELETE EventsTop1000
        OUTPUT DELETED.* 
        INTO EventsBackup;

    IF (@@ROWCOUNT = 0)
        BREAK;
END

Это атомарно и последовательно.

person Remus Rusanu    schedule 14.05.2009
comment
Если столбцы в исходной таблице не в том же порядке, что и в целевой таблице, укажите имена столбцов как select top 1000 (col1, col2) ... вывод удален (col1, col2) в резервные копии событий (col1, col2) - person Kishore A; 29.06.2021

используйте INSERT с предложением OUTPUT INTO для хранения идентификаторов вставленных строк, затем DELETE, присоединяясь к этой временной таблице, чтобы удалить только эти идентификаторы

DECLARE @TempTable (YourKeyValue KeyDatatype not null)

INSERT INTO EventsBackups
    (columns1,column2, column3)
    OUTPUT INSERTED.primaryKeyValue
    INTO @TempTable
    SELECT
        top 1000
        columns1,column2, column3
        FROM Events

DELETE Events
    FROM Events
        INNER JOIN @TempTable  t ON Events.PrimaryKey=t.YourKeyValue 
person KM.    schedule 14.05.2009
comment
Мне нравится это решение. Обратите внимание, что ваше окончательное соединение будет: ON Events.PrimaryKey = t.primaryKeyValue, а не ON Events.PrimaryKey = t.YourKeyValue Просто для того, чтобы пример был непротиворечивым ;-) - person Aaron Alton; 14.05.2009
comment
@Aaron Alton, t. YourKeyValue происходит из моей @tempTable, которую я определяю в своем коде, нет @TempTable .primaryKeyValue. OUTPUT INSERTED.primaryKeyValue необходимо изменить на INSERTED. Это значение ключа. - person KM.; 14.05.2009
comment
Мне тоже очень нравится это решение, за исключением того, что здесь нет ключевого столбца. В таблице могут быть повторяющиеся строки с той же меткой времени :( Но мне это очень нравится, и за это стоит проголосовать. - person SqlRyan; 14.05.2009
comment
если первичного ключа нет, добавьте его, сделайте ИДЕНТИЧНОСТЬ. - person KM.; 15.05.2009
comment
Смотрите мой пост ниже. Предложение OUTPUT - хороший совет, но используйте его в DELETE, чтобы вернуть удаленные строки прямо в INSERT. Также см. Мой блог об этом: rusanu.com/2008/04/09/chained- обновления - person Remus Rusanu; 16.05.2009

Как насчет:

INSERT INTO EventsBackups
SELECT TOP 1000 * FROM Events ORDER BY YourKeyField

DELETE Events
WHERE YourKeyField IN (SELECT TOP 1000 YourKeyField FROM Events ORDER BY YourKeyField)
person Aaron Alton    schedule 14.05.2009
comment
Кстати, это идеальный случай для разделения раздвижного окна, если вы можете его использовать: weblogs.sqlteam.com/dang/archive/2008/08/30/ Это переключатель метаданных, поэтому вся загрузка может выполняться в максимум несколько секунд. - person Aaron Alton; 14.05.2009

Как насчет того, чтобы не делать все сразу?

INSERT INTO EventsBackups
SELECT * FROM Events WHERE date criteria

Тогда позже,

DELETE FROM Events
SELECT * FROM Events INNER JOIN EventsBackup on Events.ID = EventsBackup.ID

или эквивалент.

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

person John Saunders    schedule 14.05.2009
comment
Это слишком ресурсоемко, чтобы делать такую ​​массивную вставку в очень активную таблицу. Его необходимо разбить на фрагменты, чтобы предотвратить большие ожидания ресурсов. - person Aaron Alton; 14.05.2009
comment
Но будет заблокирована именно резервная таблица, а не таблица событий. Следовательно, возникает проблема с блокировкой? Позже вы можете выполнять удаление по частям, если они находятся в вашей резервной копии. - person Robin Day; 14.05.2009
comment
Я использую транзакцию, поэтому могу откатить вставку, если удаление не удалось. Я не хочу, чтобы в таблице архива появлялись какие-либо записи, которые все еще находятся в активной таблице, поскольку это может привести к дублированию позже. На самом деле я пытаюсь обойти невероятно громоздкий внутренний процесс архивирования приложения, который никогда не предназначался для работы с таким большим объемом данных, как у нас, и я хочу избежать всего, что могло бы его нарушить. - person SqlRyan; 14.05.2009

У вас есть указатель на поле даты? Если у вас нет sql, может потребоваться перейти на блокировку таблицы, которая заблокирует всех ваших пользователей во время выполнения ваших архивных операторов.

Я думаю, вам понадобится индекс, чтобы эта операция работала хорошо! Поместите индекс в поле даты и попробуйте снова!

person Noel Kennedy    schedule 14.05.2009
comment
Я использую SQL 2005, и в таблице вообще нет индексов, что делает операторы SELECT дорогими для начала. - person SqlRyan; 14.05.2009

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

person John M Gant    schedule 14.05.2009

Вот что я в итоге сделал:

SET @CleanseFilter = @startdate
WHILE @CleanseFilter IS NOT NULL
BEGIN
    BEGIN TRANSACTION

        INSERT INTO ArchiveDatabase.dbo.MyTable
        SELECT *
          FROM dbo.MyTable
         WHERE startTime BETWEEN @startdate AND @CleanseFilter

        DELETE dbo.MyTable
         WHERE startTime BETWEEN @startdate AND @CleanseFilter

    COMMIT TRANSACTION

    SET @CleanseFilter = (SELECT MAX(starttime)
                FROM (SELECT TOP 1000
                             starttime
                    FROM dbo.MyTable
                       WHERE startTime BETWEEN @startdate AND @enddate
                    ORDER BY starttime) a)
END

Я не тяну ровно 1000, просто 1000, поэтому он обрабатывает повторы в столбце времени соответствующим образом (что меня беспокоило, когда я рассматривал возможность использования ROWCOUNT). Поскольку в столбце времени часто встречаются повторы, я вижу, что он регулярно перемещает 1002 или 1004 строки за итерацию, поэтому я знаю, что он получает все.

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

person SqlRyan    schedule 14.05.2009
comment
если у вас нет ключа и вы не хотите его добавлять, используйте мой ответ, но измените его. выполните удаление с помощью OUTPUT INTO, просто захватите все столбцы во временную таблицу, а затем вставьте из выбранной этой временной таблицы. - person KM.; 15.05.2009
comment
Вы должны быть очень осторожны с отправленным вами SQL. Если вы не используете изоляцию транзакций SERIALIZABLE, ваш DELETE не будет гарантированно удалять только строки, выбранные вашим SELECT. Найдите неповторяющиеся чтения и фантомные чтения. Если вы все же воспользуетесь отправленным вами SQL, единственный способ гарантировать, что SERIALIZABLE будет гарантирован SQL-сервером (без индекса в столбце даты), - это блокировка таблицы, которая убьет производительность, как будто она никогда не была убита раньше! - person Noel Kennedy; 16.05.2009
comment
Невозможно редактировать комментарии * для выполнения уровня изоляции Serializable - person Noel Kennedy; 16.05.2009

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

Таким образом, EventsBackup всегда в актуальном состоянии, и все, что вы делаете, - это периодически удаляете записи из своей таблицы событий.

person Ron Savage    schedule 14.05.2009