Подтвердить что ты не робот

Массовые вставки занимают больше времени, чем ожидалось, используя 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();
4b9b3361

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

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