在阅读this article后,我决定仔细研究一下我使用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();
答案 0 :(得分:68)
使用这种方法,我在4秒内获得的最佳成绩是50k记录
SqlTransaction trans = connection.BeginTransaction();
connection.Execute(@"
insert Member(Username, IsActive)
values(@Username, @IsActive)", members, transaction: trans);
trans.Commit();
答案 1 :(得分:10)
我最近遇到了这个问题,并注意到在连接打开后创建了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();
}
答案 2 :(得分:1)
我创建了一个扩展方法,使您可以非常快速地进行批量插入。
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 },
...
});
这是非常原始的,还有进一步改进的空间,例如传递事务或commandTimeout值,但这对我有用。
答案 3 :(得分:0)
我发现所有这些例子都不完整。
以下是一些在使用后正确关闭连接的代码,并且还可以根据此主题中更近期和更好的答案正确使用transactionscope来增强Excecute性能。
using (var scope = new TransactionScope())
{
Connection.Open();
Connection.Execute(sqlQuery, parameters);
scope.Complete();
}
答案 4 :(得分:-1)
仅使用一个插入语句的Execute
方法永远不会进行批量插入或高效。即使是Transaction
的已接受答案也不会Bulk Insert
。
如果您要执行Bulk Insert
,请使用SqlBulkCopy
https://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlbulkcopy
你找不到比这更快的东西。
免责声明:我是该项目的所有者Dapper Plus
此项目不是免费的,但提供所有批量操作:
(在引擎盖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);
我们的图书馆支持多个提供商:
答案 5 :(得分:-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会添加一组行,而不是一次添加一行。瓶颈不在于写入数据,而是在日志中写下您正在做的事情。
另外,请查看最小化记录事务的规则。