为什么 Dapper 的批量插入比我预期的要慢很多?

Posted dotNET跨平台

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了为什么 Dapper 的批量插入比我预期的要慢很多?相关的知识,希望对你有一定的参考价值。

咨询区

  • kenwarner

我的项目中有一个批量插入的需求,我采用的是 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();
}

上面代码插入需要 20s,插入量大概 2500/s,虽效果还行,但我在网上找的文章说可以做到 45k/s ,请问如何做到这么高的插入量?

回答区

  • Fredrik Ljung

下面是我批量插入的最佳实践,可以实现 50k 条数据 4s 搞定。

SqlTransaction trans = connection.BeginTransaction();

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

trans.Commit();
  • CallumVass

我实现了一个可批量插入的扩展方法,参考代码如下:

public static class DapperExtensions
{
    public static async Task BulkInsert<T>(
        this IDbConnection connection,
        string tableName,
        IReadOnlyCollection<T> items,
        Dictionary<string, Func<T, object>> dataFunc)
    {
        const int MaxBatchSize = 1000;
        const int MaxParameterSize = 2000;

        var batchSize = Math.Min((int)Math.Ceiling((double)MaxParameterSize / dataFunc.Keys.Count), MaxBatchSize);
        var numberOfBatches = (int)Math.Ceiling((double)items.Count / batchSize);
        var columnNames = dataFunc.Keys;
        var insertSql = $"INSERT INTO {tableName} ({string.Join(", ", columnNames.Select(e => $"[{e}]"))}) VALUES ";
        var sqlToExecute = new List<Tuple<string, DynamicParameters>>();

        for (var i = 0; i < numberOfBatches; i++)
        {
            var dataToInsert = items.Skip(i * batchSize)
                .Take(batchSize);
            var valueSql = GetQueries(dataToInsert, dataFunc);

            sqlToExecute.Add(Tuple.Create($"{insertSql}{string.Join(", ", valueSql.Item1)}", valueSql.Item2));
        }

        foreach (var sql in sqlToExecute)
        {
            await connection.ExecuteAsync(sql.Item1, sql.Item2, commandTimeout: int.MaxValue);
        }
    }

    private static Tuple<IEnumerable<string>, DynamicParameters> GetQueries<T>(
        IEnumerable<T> dataToInsert,
        Dictionary<string, Func<T, object>> dataFunc)
    {
        var parameters = new DynamicParameters();

        return Tuple.Create(
            dataToInsert.Select(e => $"({string.Join(", ", GenerateQueryAndParameters(e, parameters, dataFunc))})"),
            parameters);
    }

    private static IEnumerable<string> GenerateQueryAndParameters<T>(
        T entity,
        DynamicParameters parameters,
        Dictionary<string, Func<T, object>> dataFunc)
    {
        var paramTemplateFunc = new Func<Guid, string>(guid => $"@p{guid.ToString().Replace("-", "")}");
        var paramList = new List<string>();

        foreach (var key in dataFunc)
        {
            var paramName = paramTemplateFunc(Guid.NewGuid());
            parameters.Add(paramName, key.Value(entity));
            paramList.Add(paramName);
        }

        return paramList;
    }
}

然后可以像下面这样使用。

await dbConnection.BulkInsert( "MySchemaName.MyTableName", myCollectionOfItems,
    new Dictionary<string, Func<MyObjectToInsert, object>>
        {
            { "ColumnOne", u => u.ColumnOne },
            { "ColumnTwo", u => u.ColumnTwo },
            ...
        });

点评区

在实际开发中,批量插入是一个非常常见的场景,用 事务拼sql 都是高效的方式,我在实际开发中,用的是事务方式,学习了。

以上是关于为什么 Dapper 的批量插入比我预期的要慢很多?的主要内容,如果未能解决你的问题,请参考以下文章

SELECT DISTINCT 在我的 PostgreSQL 表上比预期的要慢

为啥我的正则表达式模式收集的比我预期的要多?

如何优化 MySQL 视图

boost::this_thread::sleep_for 的睡眠时间比我预期的要长得多。

类型安全连接与 Spark 数据集的安全性比我预期的要低

Hoare分区算法索引超出范围