Объемные вставки занимают больше времени, чем ожидалось с помощью Dapper
после прочтения в этой статье я решил поближе взглянуть на то, как я использовал Dapper.
Я запустил этот код на пустой базе
var members = new List<Member>();
for (int i = 0; i < 50000; i++)
{
members.Add(new Member()
{
Username = i.toString(),
IsActive = true
});
}
using (var scope = new TransactionScope())
{
connection.Execute(@"
insert Member(Username, IsActive)
values(@Username, @IsActive)", members);
scope.Complete();
}
это заняло около 20 секунд. Это 2500 вставок в секунду. Неплохо, но не очень хорошо, учитывая, что блог достигал 45k вставок/сек. Есть ли более эффективный способ сделать это в Dapper?
кроме того, в качестве примечания, запуск этого кода через отладчик Visual Studio взял более 3 минут! я подумал, что отладчик немного замедлит его, но я был очень удивлен, увидев это.
обновление
это
using (var scope = new TransactionScope())
{
connection.Execute(@"
insert Member(Username, IsActive)
values(@Username, @IsActive)", members);
scope.Complete();
}
и
connection.Execute(@"
insert Member(Username, IsActive)
values(@Username, @IsActive)", members);
оба заняли 20 секунд.
но это заняло 4 секунды!
SqlTransaction trans = connection.BeginTransaction();
connection.Execute(@"
insert Member(Username, IsActive)
values(@Username, @IsActive)", members, transaction: trans);
trans.Commit();
5 ответов:
лучшее, что я смог достичь, это 50k записей за 4 секунды, используя этот подход
SqlTransaction trans = connection.BeginTransaction(); connection.Execute(@" insert Member(Username, IsActive) values(@Username, @IsActive)", members, transaction: trans); trans.Commit();
Я недавно наткнулся на это и заметил, что TransactionScope создается после открытия соединения (я предполагаю, что это так как Dappers Execute не открывает соединение, в отличие от запроса). Согласно ответу Q4 здесь:https://stackoverflow.com/a/2886326/455904 это не приведет к соединению, которое будет обрабатываться TransactionScope. Мой напарник сделал несколько быстрых тестов, и открытие соединения вне TransactionScope резко уменьшилось спектакль.
поэтому изменение на следующее должно работать:
// Assuming the connection isn't already open using (var scope = new TransactionScope()) { connection.Open(); connection.Execute(@" insert Member(Username, IsActive) values(@Username, @IsActive)", members); scope.Complete(); }
Я нашел все эти примеры неполной.
вот некоторый код, который правильно закрывает соединение после использования, а также правильно использует transactionscope для повышения производительности Excecute, основываясь на более поздних и лучших ответах в этом потоке.
using (var scope = new TransactionScope()) { Connection.Open(); Connection.Execute(sqlQuery, parameters); scope.Complete(); }
С помощью
Execute
метод только с одним оператором insert никогда не будет выполнять массовую вставку или быть эффективным. Даже принятый ответ сTransaction
неBulk Insert
.если вы хотите выполнить
Bulk Insert
используйтеSqlBulkCopy
https://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlbulkcopyвы не найдете ничего быстрее, чем это.
Щеголь Плюс
отказ от ответственности: Я владелец проект Щеголь Плюс
этот проект не является бесплатным, но предлагает все массовые операции:
- BulkInsert
- BulkUpdate
- BulkDelete
- BulkMerge
(используйте под капотом
SqlBulkCopy
)и еще несколько параметров, таких как вывод значений идентификаторов:
// CONFIGURE & MAP entity DapperPlusManager.Entity<Order>() .Table("Orders") .Identity(x => x.ID); // CHAIN & SAVE entity connection.BulkInsert(orders) .AlsoInsert(order => order.Items); .Include(x => x.ThenMerge(order => order.Invoice) .AlsoMerge(invoice => invoice.Items)) .AlsoMerge(x => x.ShippingAddress);
наша библиотека поддерживает несколько провайдеров:
- SQL Server
- SQL Компактный
- Oracle
- MySql
- PostgreSQL
- SQLite
- Жар
самый быстрый вариант для меня:
var dynamicParameters = new DynamicParameters(); var selects = new List<string>(); for (var i = 0; i < members.Length; i++) { var member = members[i]; var pUsername = $"u{i}"; var pIsActive = $"a{i}"; dynamicParameters.Add(pUsername, member.Username); dynamicParameters.Add(pIsActive, member.IsActive); selects.Add("select @{pUsername},@{pIsActive}"); } con.Execute($"insert into Member(Username, IsActive){string.Join(" union all ", selects)}", dynamicParameters);
которые генерируют sql, как:
INSERT TABLENAME (Column1,Column2,...) SELECT @u0,@a0... UNION ALL SELECT @u1,@a1... UNION ALL SELECT @u2,@a2...
этот запрос работает быстрее, потому что sql добавляет набор строк вместо добавления 1 строки за раз. Узким местом является не запись данных, а запись того, что вы делаете в журнале.
кроме того, ознакомьтесь с правилами минимально зарегистрированных транзакций.