Производительность вставки SQL Server
У меня есть запрос на вставку, который создается таким образом
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);
Этот запрос генерируется только для строк 110K.
Для выполнения всех этих запросов требуется 30 минут
Я проверил план запроса, и наибольшие% узлов
Кластеризованный индекс Вставка при 57% стоимости запроса
который имеет длинный xml, который я не хочу публиковать.
Столовая катушка, которая составляет 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 TABLENAME NOCHECK ОГРАНИЧИВАЕТ ВСЕ
Перед запросами, а затем
ALTER 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. Это кажется намного более гибким и быстрым.
Ответы
Ответ 1
Похоже, что вставки заставляют SQL Server пересчитывать индексы. Одним из возможных решений могло бы стать падение индекса, выполнение вставки и повторное добавление индекса. При попытке вашего решения, даже если вы скажете ему игнорировать ограничения, ему все равно нужно будет обновлять индекс.
Ответ 2
Выполняете ли вы эти запросы по одному от клиента .NET(т.е. отправляете 110 000 отдельных запросов запросов на SQL Server)?
В этом случае вероятно, что это латентность сети и другие накладные расходы на отправку этих ВСТАВКИ на SQL Server без их пакетной обработки, а не сам SQL Server.
Отметьте BULK INSERT.
Ответ 3
Скорее всего, это фиксация флеша. Если вы не переносите наборы INSERT в транзакцию с явным управлением, каждый INSERT является своей собственной транзакцией с автоматическим подтверждением. Значение каждого INSERT автоматически фиксирует фиксацию, и фиксация должна ждать, пока журнал будет долговечен (т.е. записан на диск). Промывка журнала после каждой вставки очень медленная.
Например, пытаясь вставить 100k строк, подобных вашим, в стиле фиксации одной строки:
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.
Ответ 4
Вы отметили этот вопрос как "bulkinsert". Итак, почему бы не использовать команду BULK INSERT?
Если вы хотите обновления прогресса, вы можете разделить объемную вставку на более мелкие части и обновить прогресс после завершения каждой части.
Ответ 5
Есть несколько вещей, которые вы можете сделать:
1) Disable any triggers on this table
2) Drop all indexes
3) Drop all foreign keys
4) Disable any check constraints
Ответ 6
Запуск отдельных INSERT всегда будет самым медленным вариантом. Кроме того, какая сделка с @@IDENTITY - не похожа на то, что вы отслеживали тех, кто находится между ними.
Если вы не хотите использовать BULK INSERT из файла или SSIS, в ADO.NET есть функция SqlBulkCopy, которая, вероятно, сделайте все возможное, если вам абсолютно необходимо сделать это из .NET-программы.
Строки 110k должны занимать меньше времени для импорта, чем при повторном подключении и написании этого ответа.
Ответ 7
Некоторые предложения по увеличению производительности вставки:
- Увеличить пакет ADO.NET BatchSize
- Удобно использовать кластеризованный индекс целевой таблицы, чтобы вставки не приводили к кластерному индексу node splits (например, столбцу autoinc)
- Сначала введите во временную таблицу кучи, затем введите один большой оператор "вставить-выберете", чтобы переместить все данные промежуточной таблицы в фактическую таблицу целей.
- Применить SqlBulkCopy
- Поместите блокировку таблицы перед вставкой (если это позволяет ваш бизнес-сценарий)
Взято из Советы по быстрому встраиванию встраивания на SqlServer
Ответ 8
Hm, пусть он запустится, проверьте счетчики производительности. что ты видишь? Какая дисковая компоновка у вас есть? Я могу вставить миллион строк за 30 минут - точнее, сто миллионов миллиметров строк (финансовая информация в реальном времени, ссылки на 3 другие таблицы). Я почти уверен, что ваш макет ввода-вывода плох (т.е. Плохая структура диска, плохое распределение файлов)