Объемные вставки занимают больше времени, чем ожидалось с помощью 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 59

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 используйте SqlBulkCopyhttps://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 строки за раз. Узким местом является не запись данных, а запись того, что вы делаете в журнале.

кроме того, ознакомьтесь с правилами минимально зарегистрированных транзакций.