维护公司老项目,所用技术为ASP.NET,某张报表点击链接后查询当天数据(共100条左右),需查询1分钟以上,故进行问题排查与优化,拉取代码后用Visual Studio打开,启动其自带的【性能探查器】勾选【检查选项】,之后本地打开项目网站,点击问题报表链接,待报表加载完成后停止分析,查看报告。
报告内容分析:
通过报告很容易定位到相关的Controller中的方法,大部分耗时都在这两个方法中,通过分析这两个方法以及调用的位置,找到了出现问题的原因,源代码型如下:
foreach(Item item in Items){
//此处Model代指出现问题的实体类,该实体类为树状结构,有字段ParentId等
//LoadById(int id)方法为通过Id查询数据库获取该实体类
//GetAllSub()方法为静态方法,model.GetAllSub()可获取该实体类的所有树状子节点集合 返回List<Model>
Model model =LoadById(item.ModelId);
if(model!=null){
List<Model> subModels = model.GetAllSub();
for(Model model in subModels){
//逻辑处理
}
}
}
通过这段代码其实不难看出问题所在,在一个循环中先查询了一个实体类,再去查询实体类的所有下级子节点集合,这样操作频繁的进行数据库的查询,花费了大量的时间。在性能报告中显示,查询调用了近1w多次,一次查询报表操作花费70多秒。
问题定位后,解决起来还是比较简单的,目前我所使用的方法是将对应实体类一次性查出,再在代码中建立起相应的关系,这样只需查询一次数据库。
List<Model> models = QueryModelByCondition();
//List转换为以Model.Id为Key,Model为Value的数据字典
Dictionary<int, Model> modelDictionary = models.ToDictionary(model => model.Id);
//以Model.Id为Key,该节点所有子节点的集合为Value的数据字典。
Dictionary<int, List<Model>> subModelDictionary = new Dictionary<int, List<Model>>();
foreach(Model model in models){
int key = model.Id;
if(!subModelDictionary.ContainsKey(key)){
//若没有对应key,则初始化一个
subModelDictionary.add(key,new List<Model>());
}
//将符合条件的当前元素添加进入集合
if(model.ParentId!=0&&subModelDictionary.ContainsKey(model.ParentId)){
subModelDictionary[key].add(model);
}
}
foreach(Item item in Items){
// Model model =LoadById(item.ModelId);
Model model = null;
if(modelDictionary.ContainsKey(item.ModelId)){
model=modelDictionary[item.ModelId];
}
if(model!=null){
//List<Model> subModels = model.GetAllSub();
List<Model> subModels = new List<Model>();
if(subModelDictionary.ContainsKey(item.ModelId)){
subModels=subModelDictionary[item.ModelId];
}
for(Model model in subModels){
//逻辑处理
}
}
}
通过代码处理,减少了数据库查询次数,优化后:
总耗时从70多s降到了0.8s。