Массовые вставки занимают больше времени, чем ожидалось, используя 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();
Ответы
Ответ 1
Лучшее, что я смог достичь, - это 50 тыс. записей за 4 секунды, используя этот подход
SqlTransaction trans = connection.BeginTransaction();
connection.Execute(@"
insert Member(Username, IsActive)
values(@Username, @IsActive)", members, transaction: trans);
trans.Commit();
Ответ 2
Я недавно споткнулся об этом и заметил, что TransactionScope создается после открытия соединения (я предполагаю это, поскольку Dappers Execute не открывает соединение, в отличие от Query). В соответствии с ответом Q4 здесь: fooobar.com/questions/35486/..., что не приведет к тому, что соединение будет обрабатываться 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
Я нашел все эти примеры неполными.
Вот код, который правильно закрывает соединение после использования, а также правильно использует транзакцию для повышения производительности Excecute на основе более свежих и лучших ответов в этом потоке.
using (var scope = new TransactionScope())
{
Connection.Open();
Connection.Execute(sqlQuery, parameters);
scope.Complete();
}
Ответ 4
самый быстрый вариант для меня:
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 строке за раз. Узкое место не записывает данные, он пишет, что вы делаете в журнале.
Также рассмотрите правила минимально регистрируемых транзакций.