Производительность вставки SQL Server - PullRequest
19 голосов
/ 16 марта 2010

У меня есть запрос на вставку, который генерируется следующим образом:

INSERT INTO InvoiceDetail (LegacyId,InvoiceId,DetailTypeId,Fee,FeeTax,Investigatorid,SalespersonId,CreateDate,CreatedById,IsChargeBack,Expense,RepoAgentId,PayeeName,ExpensePaymentId,AdjustDetailId) 
VALUES(1,1,2,1500.0000,0.0000,163,1002,'11/30/2001 12:00:00 AM',1116,0,550.0000,850,NULL,@ExpensePay1,NULL); 
DECLARE @InvDetail1 INT; SET @InvDetail1 = (SELECT @@IDENTITY);

Этот запрос генерируется только для строк размером 110 КБ.

Выполнение всех этих запросов занимает 30 минут

Я проверил план запроса, и самые большие% узлов:

Вставка кластерного индекса при 57% стоимости запроса в котором есть длинный xml, который я не хочу публиковать.

Таблица Spool, которая составляет 38% стоимости запроса

<RelOp AvgRowSize="35" EstimateCPU="5.01038E-05" EstimateIO="0" EstimateRebinds="0" EstimateRewinds="0" EstimateRows="1" LogicalOp="Eager Spool" NodeId="80" Parallel="false" PhysicalOp="Table Spool" EstimatedTotalSubtreeCost="0.0466109">
  <OutputList>
    <ColumnReference Database="[SkipPro]" Schema="[dbo]" Table="[InvoiceDetail]" Column="InvoiceId" />
    <ColumnReference Database="[SkipPro]" Schema="[dbo]" Table="[InvoiceDetail]" Column="InvestigatorId" />
    <ColumnReference Column="Expr1054" />
    <ColumnReference Column="Expr1055" />
  </OutputList>
  <Spool PrimaryNodeId="3" />
</RelOp>

Итак, мой вопрос: что я могу сделать, чтобы улучшить скорость этой вещи? Я уже бегаю ALTER TABLE TABLENAME NOCHECK СОДЕРЖИТ ВСЕ Перед запросами, а затем ALTER TABLE TABLENAME NOCHECK СОДЕРЖИТ ВСЕ после запросов.

И это не с трудом сбрило время.

Знайте, что я выполняю эти запросы в приложении .NET, которое использует объект SqlCommand для отправки запроса.

Затем я попытался вывести команды sql в файл, а затем выполнить его с помощью sqlcmd, но я не получил никаких обновлений о том, как это происходит, поэтому я отказался от этого.

Есть идеи, советы или помощь?

ОБНОВЛЕНИЕ:

Хорошо, так что вы все очень помогли. В этой ситуации хотелось бы отдать должное более чем одному ответу.

Решение этой проблемы было двояким.

Первый:

1) Я отключил / включил все внешние ключи (намного проще, чем их сбросить)

ALTER TABLE TableName NOCHECK CONSTRAINT ALL
ALTER TABLE TableName CHECK CONSTRAINT ALL

2) Я отключил / снова включил индексы (опять же намного проще, чем сброс)

ALTER INDEX [IX_InvoiceDetail_1] ON [dbo].[InvoiceDetail] DISABLE
ALTER INDEX [IX_InvoiceDetail_1] ON [dbo].[InvoiceDetail] REBUILD PARTITION = ALL WITH ( PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON, ONLINE = OFF, SORT_IN_TEMPDB = OFF )

Второй:

Я заключил все операторы вставки в одну транзакцию. Я изначально не знал, как это сделать в .NET.

Я действительно ценю все полученные данные.

Если я когда-нибудь сделаю такой перевод из БД в БД, я обязательно начну с BULK INSERT. Кажется, гораздо гибче и быстрее.

Ответы [ 8 ]

14 голосов
/ 16 марта 2010

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

10 голосов
/ 16 марта 2010

Выполняете ли вы эти запросы по одному с клиента .Net (т.е. отправляете 110 000 отдельных запросов на SQL Server)?

В этом случае вполне вероятно, что это сетевая задержка и другие издержки отправки этих INSERT на SQL Server без их пакетной обработки, а не самого SQL Server.

Проверьте BULK INSERT.

9 голосов
/ 17 марта 2010

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

Например, при попытке вставить 100 тыс. Строк, подобных вашей, в стиле фиксации одной строки:

set nocount on; 
declare @start datetime = getutcdate();  

declare @i int = 0;
while @i < 100000
begin
INSERT INTO InvoiceDetail (
  LegacyId,InvoiceId,DetailTypeId,Fee,
  FeeTax,Investigatorid,SalespersonId,
  CreateDate,CreatedById,IsChargeBack,
  Expense,RepoAgentId,PayeeName,ExpensePaymentId,
  AdjustDetailId) 
  VALUES(1,1,2,1500.0000,0.0000,163,1002,
    '11/30/2001 12:00:00 AM',
    1116,0,550.0000,850,NULL,1,NULL); 
  set @i = @i+1;
end

select datediff(ms, @start, getutcdate());

На моем сервере это занимает около 12 секунд. Но если добавить управление транзакциями и зафиксировать каждые 1000 строк, вставка строк по 100 тыс. Длится всего около 4 с:

set nocount on;  
declare @start datetime = getutcdate();  

declare @i int = 0;
begin transaction
while @i < 100000
begin
INSERT INTO InvoiceDetail (
  LegacyId,InvoiceId,DetailTypeId,
  Fee,FeeTax,Investigatorid,
  SalespersonId,CreateDate,CreatedById,
  IsChargeBack,Expense,RepoAgentId,
  PayeeName,ExpensePaymentId,AdjustDetailId) 
  VALUES(1,1,2,1500.0000,0.0000,163,1002,
    '11/30/2001 12:00:00 AM',
    1116,0,550.0000,850,NULL,1,NULL); 
  set @i = @i+1;
  if (@i%1000 = 0)
  begin
    commit
    begin transaction
  end  
end
commit;
select datediff(ms, @start, getutcdate());

Также, учитывая, что я могу вставить 100 тыс. Строк за 12 секунд даже без пакетной фиксации, в то время как вам нужно 30 минут, стоит изучить 1) скорость вашей подсистемы ввода-вывода (например, какую Avg. Sec per Transaction вы видите на диски) и 2) что еще делает код клиента между извлечением идентификатора @@ из одного вызова и вызовом следующей вставки. Возможно, большая часть времени находится на стороне клиента в стеке. Одним простым решением было бы запустить несколько вставок параллельно (BeginExecuteNonQuery), чтобы вы постоянно подавали вставки SQL Server.

6 голосов
/ 16 марта 2010

Вы пометили этот вопрос как "bulkinsert". Так почему бы не использовать команду BULK INSERT ?

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

4 голосов
/ 16 марта 2010

Выполнение отдельных INSERT всегда будет самым медленным вариантом. Кроме того - в чем дело с @@ IDENTITY - не похоже, что вам нужно отслеживать эти промежуточные значения.

Если вы не хотите использовать BULK INSERT из файла или службы SSIS, в ADO.NET есть функция SqlBulkCopy, которая, вероятно, будет вашим лучшим выбором, если вам абсолютно необходимо сделать это изнутри Программа .NET.

Строки в 110 тыс. Должны занимать меньше времени, чем я, чтобы найти и написать этот ответ.

4 голосов
/ 16 марта 2010

Вы можете сделать несколько вещей:

1) Disable any triggers on this table
2) Drop all indexes
3) Drop all foreign keys
4) Disable any check constraints
3 голосов
/ 07 октября 2011

Некоторые предложения по увеличению производительности вставки:

  • Увеличение размера пакета ADO.NET
  • Выбор кластеризованного индекса целевой таблицы разумно, чтобы вставки не приводили к расщеплению узлов кластеризованного индекса(например, столбец autoinc)
  • Сначала вставьте во временную таблицу кучи, а затем выполните один большой оператор "вставка по выбору", чтобы поместить все эти данные промежуточной таблицы в фактическую целевую таблицу
  • ApplySqlBulkCopy
  • Поместите блокировку таблицы перед вставкой (если это позволяет ваш бизнес-сценарий)

Взято из Советы по быстродействию молниеносной вставки на SqlServer

1 голос
/ 16 марта 2010

Хм, пусть работает, проверьте счетчики производительности. что ты видишь? Какое расположение дисков у вас есть? Я могу вставить несколько миллионов строк за 30 минут - точнее, около ста миллионов строк (финансовая информация в реальном времени, ссылки на 3 другие таблицы). Бьюсь об заклад, что ваша структура ввода-вывода плохая (т.е. плохая структура диска, плохое распределение файлов)

...