Пакетная фиксация при большой операции INSERT в нативном SQL? - PullRequest
7 голосов
/ 21 октября 2009

У меня есть пара больших таблиц (строки 188 м и 144 м), которые мне нужно заполнить из представлений, но каждое представление содержит несколько сотен миллионов строк (объединяя данные с псевдоразмерным моделированием в плоскую форму). Ключи в каждой таблице - это более 50 составных байтов столбцов. Если бы данные были в таблицах, я всегда мог подумать об использовании sp_rename для создания другой новой таблицы, но на самом деле это не вариант.

Если я выполняю одну операцию INSERT, процесс использует огромное количество места в журнале транзакций, как правило, заполняя его и вызывая кучу хлопот с администраторами баз данных. (И да, это, вероятно, работа, которую администраторы БД должны выполнять / дизайн / архитектор)

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

Есть ли что-нибудь, кроме разделения процесса на несколько операций INSERT, используя какой-то ключ для распределения строк по разным пакетам и выполнения цикла?

Ответы [ 6 ]

5 голосов
/ 21 октября 2009

Имеет ли представление ЛЮБОЙ вид уникального идентификатора / ключа-кандидата? Если это так, вы можете выбрать эти строки в рабочую таблицу, используя:

SELECT key_columns INTO dbo.temp FROM dbo.HugeView;

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

Затем вы можете сделать что-то вроде этого, вставляя 10000 строк за раз, и резервное копирование журнала между:

SET NOCOUNT ON;

DECLARE
    @batchsize INT,
    @ctr INT,
    @rc INT;

SELECT
    @batchsize = 10000,
    @ctr = 0;

WHILE 1 = 1
BEGIN
    WITH x AS
    (
        SELECT key_column, rn = ROW_NUMBER() OVER (ORDER BY key_column)
        FROM dbo.temp
    )
    INSERT dbo.PrimaryTable(a, b, c, etc.)
        SELECT v.a, v.b, v.c, etc.
        FROM x
        INNER JOIN dbo.HugeView AS v
        ON v.key_column = x.key_column
        WHERE x.rn > @batchsize * @ctr
        AND x.rn <= @batchsize * (@ctr + 1);

    IF @@ROWCOUNT = 0
        BREAK;

    BACKUP LOG PrimaryDB TO DISK = 'C:\db.bak' WITH INIT;

    SET @ctr = @ctr + 1;
END

Это все, что у меня в голове, так что не руби / вставляй / беги, но я думаю, что общая идея есть.

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

5 голосов
/ 21 октября 2009

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

create cursor ....
select YEAR(DateCol), MONTH(DateCol) from whatever

while ....
    insert into yourtable(...)
    select * from whatever 
    where YEAR(DateCol) = year and MONTH(DateCol) = month
end
3 голосов
/ 09 декабря 2013

Я знаю, что это старая ветка, но я сделал общую версию решения для курсора Артура:

--Split a batch up into chunks using a cursor.
--This method can be used for most any large table with some modifications
--It could also be refined further with an @Day variable (for example)

DECLARE @Year INT
DECLARE @Month INT

DECLARE BatchingCursor CURSOR FOR
SELECT DISTINCT YEAR(<SomeDateField>),MONTH(<SomeDateField>)
FROM <Sometable>;


OPEN BatchingCursor;
FETCH NEXT FROM BatchingCursor INTO @Year, @Month;
WHILE @@FETCH_STATUS = 0
BEGIN

--All logic goes in here
--Any select statements from <Sometable> need to be suffixed with:
--WHERE Year(<SomeDateField>)=@Year AND Month(<SomeDateField>)=@Month   


  FETCH NEXT FROM BatchingCursor INTO @Year, @Month;
END;
CLOSE BatchingCursor;
DEALLOCATE BatchingCursor;
GO

Это решило проблему с нагрузками на наши большие таблицы.

2 голосов
/ 21 октября 2009

Пикси-пыли нет, вы это знаете.

Не зная специфики фактической передаваемой схемы, общее решение будет именно таким, как вы его описываете: разделите обработку на несколько вставок и отслеживайте ключ (ы). Это своего рода псевдокод T-SQL:

create table currentKeys (table sysname not null primary key, key sql_variant not null);
go

declare @keysInserted table (key sql_variant);
declare @key sql_variant;
begin transaction
do while (1=1)
begin
    select @key = key from currentKeys where table = '<target>';
    insert into <target> (...)
    output inserted.key into @keysInserted (key)
    select top (<batchsize>) ... from <source>
    where key > @key
    order by key;

    if (0 = @@rowcount)
       break; 

    update currentKeys 
    set key = (select max(key) from @keysInserted)
    where table = '<target>';
    commit;
    delete from @keysInserted;
    set @key = null;
    begin transaction;
end
commit

Будет сложнее, если вы захотите использовать параллельные пакеты и разделить ключи.

1 голос
/ 21 октября 2009

Это похоже на работу для доброго старика ПП .

1 голос
/ 21 октября 2009

Вы можете использовать команду BCP для загрузки данных и использовать параметр Batch Size

http://msdn.microsoft.com/en-us/library/ms162802.aspx

Двухступенчатый процесс

  • Данные BCP OUT из представлений в текстовые файлы
  • Данные BCP IN из текстовых файлов в таблицы с параметром размера партии
...