记一次事务超时
起因
最近遇到线上一个问题,无法保存成功,记录的错误日志,是事务超时(事务超时时间为600秒).这个功能逻辑其实比较复杂的,一直想优化(还没来的急,早期数据没那么多),查询了很多不同的数据,放在内存中,让后进行各种计算.最终在把结果保存到数据库中.因为这一块整体都是使用EF处理的.
- 查询各种数据,放入List中(这个数据有几十万条)
- 进行各种计算,修改数据(这个也要几十万条)
- 计算后的结果保存到数据库中(插入的数据有两三万条)
2和3在方法执行完成后提交事务,都是逐条修改的,排除原因后,发现主要有2个地方比较耗时.
1. FirstOrDefault竟然是瓶颈
//模拟实体
public class Person
{
public string Code { get; set; }
public string Name { get; set; }
}
//模拟代码
private void TestData()
{
List<Person> list1 = new List<Person>(); //50万条数据
List<Person> list2 = new List<Person>(); //1万条数据
for (int i = 0;i< list1.Count;i++)
{
var item = list1[i];
var val = list2.FirstOrDefault(p => p.Code == item.Code &&
p.Name == item.Name); //FirstOrDefault这里很耗时
}
}
当集合数据比较少的时候,使用FirstOrDefault查找是没有问题的,FirstOrDefault内部也是循坏遍历的,最好的情况,是第一个匹配成功,最坏的情况,就是循坏查找了一万次.我们看一下FirstOrDefault源码:
public static TSource FirstOrDefault<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
if (source == null) throw Error.ArgumentNull("source");
if (predicate == null) throw Error.ArgumentNull("predicate");
foreach (TSource element in source)
{
if (predicate(element)) return element;
}
return default(TSource);
}
解决方法,就是修改list2由List为Dictionary类型.
private void TestData()
{
List<Person> list1 = new List<Person>(); //50万条数据
//改为Dictionary,设置初始化大小和不区分key的大小写
Dictionary<string, Person> maps = new Dictionary<string, Person>(100000, StringComparer.OrdinalIgnoreCase);
for (int i = 0; i < list1.Count; i++)
{
var item = list1[i];
var key = $"{item.Code}{item.Name}";
if (maps.TryGetValue(key, out Person person))
{
//处理计算逻辑
}
}
}
修改之后,这一块处理的速度,不再是瓶颈,还有两个问题,修改数据改成批量更新,每次执行2000条数据.另外一个就是批量插入
2. 使用SqlBulkCopy批量插入
private async Task BatchInsert(SqlConnection con, DataTable dataTable, string tableName)
{
using (var bulkCopy = new SqlBulkCopy(con))
{
bulkCopy.BatchSize = 2000; //每次执行2000条插入
bulkCopy.DestinationTableName = tableName;
//将DataTable的列名映射到SqlBulkCopy列
foreach (DataColumn colInfo in dataTable.Columns)
{
bulkCopy.ColumnMappings.Add(colInfo.ColumnName, colInfo.ColumnName);
}
//异步写入到数据库中
await bulkCopy.WriteToServerAsync(dataTable);
}
}
结果
通过这几种方式如减少查找时间和批量修改数据,及批量插入.这一块功能在一段时间不会成为瓶颈.当工作中遇到性能瓶颈的时候,不能靠主观的猜测,需要用工具和数据分析,让后针对性的去优化.
秋风
2023-03-19