2012-05-21 4 views
56

После прочтения this article Я решил более внимательно посмотреть на то, как я использовал 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 минут! Я решил, что отладчик немного замедлит его, но я был очень удивлен, увидев это.

UPDATE

Так что

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(); 
+2

Вы пробовали DbTransaction (SqlTransaction)? Это немного меньше накладных расходов. Кроме того, просто для чисел: возможно, попробуйте и без транзакции, поэтому мы знаем, что мы измеряем. Наконец, что же происходит в 20-е годы? Вставки? Вставки + завершены? Все? Что-то другое? –

+0

20s - это только часть, охватываемая оператором using. Я попробую SqlTransaction – kenwarner

+0

. Другой тип транзакции не может сэкономить больше нескольких миллисекунд. Это разовая стоимость, не пропорциональная количеству предметов. – usr

ответ

56

Лучшее, что я был в состоянии достигнуть было 50к записей в 4 секунды, используя этот подход

SqlTransaction trans = connection.BeginTransaction(); 

connection.Execute(@" 
insert Member(Username, IsActive) 
values(@Username, @IsActive)", members, transaction: trans); 

trans.Commit(); 
+1

Возможно, соединение не зачислен в транзакцию, и у вас возникла проблема раньше? – GorillaApe

+0

Я пробовал ваш код, и у меня было 100% улучшение. 10 сек. От 20 – GorillaApe

+10

@Parhs: это 50% улучшение. – skolima

10

Я споткнулся accross это недавно и заметил, что TransactionScope создается после того, как соединение открыто (я предполагаю, это, поскольку Dappers Execute не открывает соединение, в отличие от Query). В соответствии с ответом 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(); 
} 
+3

Если вы попытаетесь «ExecuteAsync», это вызовет исключение: «TransactionScope должен быть расположен в том же потоке, который был создан». Чтобы избежать этого: 'using (var scope = new TransactionScope (TransactionScopeAsyncFlowOption.Enabled))' –

-5

самый быстрый вариант для меня:

  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 строки за раз. Узкое место не записывает данные, он пишет, что вы делаете в журнале.

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

+29

[Маленькие таблицы Bobby] (https://xkcd.com/327/) говорит hi – m0sa

+0

@ m0sa, я исправил его :) – razon

0

Я нашел все эти примеры незавершенными.

Ниже приведен код, который правильно закрывает соединение после использования, а также правильно использует транзакцию для повышения производительности Excecute на основе последних и лучших ответов в этой теме.

using (var scope = new TransactionScope()) 
{ 
    Connection.Open(); 
    Connection.Execute(sqlQuery, parameters); 

    scope.Complete(); 
} 
+0

используя ключевое слово автоматически вызывает IDispose, который вызывает .Close(). Ключевое слово using преобразует ваш код во время компиляции в try/finally, где .Dispose() вызывается в конце. –

+0

Я думал, что IDispose называется Dispose? –

+0

Вы правы. Он вызывает Dispose, но реализация соединений Dispose вызывает Close(). Я также предполагаю, что это объект SqlConnection. Но да, использование помогает, поэтому вы не забудете позвонить закрытым, так как он вызван в Dispose. –