筛选数据
需求:如果数据库中存在OrderNum相同,且IsDefault不同的记录,那么IsDefault值为0的记录将替换值为1的记录(IsDefault值为1的记录不展示)。
由于查出来的数据不多,100条以内,所以我是直接全部查询到List内存中,然后在内存中进行数据过滤的操作,思来想去都觉得我如下的实现方式很low,但是我一时又没想到好的办法,不知道大家有没有好的办法?
var newList = list.ToList(); //筛选出哪些排序号有重复 var orderNumList = newList.GroupBy(g => g.OrderNum).Select(g => new { orderNum = g.Key, count = g.Count() }).Where(g => g.count > 1).Select(s => s.orderNum).ToList(); var cfList = newList.Where(w => orderNumList.Contains(w.OrderNum)); //获取有重复排序号的记录 var cfDefaultList = cfList.Where(w => w.IsDefault); //默认模块记录 var cfNoDefaultList = cfList.Where(w => w.IsDefault == false); //非默认模块记录 var intersectedList = from d in cfDefaultList join f in cfNoDefaultList on d.OrderNum equals f.OrderNum where d.IsDefault!= f.IsDefault select d; var newIntersectedList = intersectedList.Distinct().ToArray(); //排序号相同,既存在默认记录也存在非默认记录的数据 if (newIntersectedList != null && newIntersectedList.Length > 0) { for (int i = 0; i < newIntersectedList.Length; i++) { if (newList.Contains(newIntersectedList[i])) { newList.Remove(newIntersectedList[i]); } } } newList = newList.OrderBy(x => x.OrderNum).ToList();
以上的newList就代码截图中的数据。
优化API接口
有一个API接口经常卡顿,而且很不稳定,快的时候2~3秒,慢的时候10秒去了。
接口需求:根据社区ID获取优惠券记录。
分析:
- 负责给API接口提供数据的系统中,缺少许多索引,存在许多慢查询视图。
- 原来的LINQ实现方式是在内存中分页,响应速度太慢。
- 并发请求的情况下,资源占用。
优化思路:
1、使用缓存
同一个社区的人在同一时间所看到的优惠券记录应该是一样的,而且我们应该允许脏读,我们在12306上面买火车票的时候,经常也会看到显示有票,但是下单又没有了,可能是使用了缓存,那么我们这里其实同样的可以采用缓存来缓解并发问题。
在WebAPI上面加缓存,那么又分为客户端缓存和服务器缓存。而我们知道,在ASP.NET WebForm和ASP.NET MVC中都是有页面输出缓存的,而在WebAPI中默认没有,从NuGet上面下载WebApi.OutputCache.V2,然后再API接口上添加
[CacheOutput(ClientTimeSpan = 5)]//, ServerTimeSpan = 5
我这里没法直接使用服务器输出缓存,那是因为无法捕获缓存变量参数。因为我们API接口的请求参数是string appParam,字符串类型的,它是一个json对象进过base64位编码,然后再进过url编码生成的字符串。
我们只能解析后,获取社区ID,然后根据社区ID来设置缓存,把社区ID+ pageIndex就作为缓存的Key,考虑到需要缓存的数据量很小,这里我直接使用.NET自带的缓存,引入命名空间:System.Web.Caching;
private static System.Web.Caching.Cache ObjCache = HttpRuntime.Cache; /// <summary> /// 设置当前指定Key的Cache值,并限定过期时间 /// </summary> /// <param name="Key">缓存Key</param> /// <param name="Obj">缓存的值</param> /// <param name="TimeOuts">超时时间(秒)</param> public static void SetCacheSeconds(string Key, object Obj, double TimeOuts) { ObjCache.Insert(Key, Obj, null, System.DateTime.Now.AddSeconds(TimeOuts), TimeSpan.Zero); } /// <summary> /// 获取当前指定Key的Cache值 /// </summary> /// <param name="Key">缓存Key</param> /// <returns>缓存的值</returns> public static object GetCache(string Key) { return ObjCache[Key]; }
缓存操作类Cache完整代码如下:

修改API接口代码:
#region added by zouqj 2017-3-7 var result = Util.Cache.GetCache(CommunityID+ pageIndex) as Result<List<GetAvailableCouponsModel>>; if (result==null) //不存在则写入缓存 { //组装参数 Dictionary<string, string> inParams = new Dictionary<string, string>(); inParams.Add("UserId", userId); inParams.Add("PageIndex", pageIndex); inParams.Add("PageSize", pageSize); inParams.Add("CommunityID", CommunityID+ pageIndex);
...
RequestParam RequestParam = GetRequestParam(methodName, inParams, AuthenticationId); var jsonContent = JsonConvert.SerializeObject(RequestParam); result = DoPost<List<GetAvailableCouponsModel>>(jsonContent, PostUrl); Util.Cache.SetCacheSeconds(CommunityID, result, 10); //写入缓存 } return result;
2、改为存储过程实现
因为这个接口的业务逻辑比较复杂,之前的Linq代码写了好长一大串,获取的记录数很多,而且还是在内存中进行分页实现,所以我将原来的LINQ实现代码修改为分页存储过程实现。
存储过程代码如下:

这里需要注意的是,存储执行是,先关闭计数,set nocount on;,然后再打开set nocount off;,这样可以提升性能。还有就是使用WITH ( NOLOCK )允许脏读,提升查询效率。
这里遇到一个很诡异的问题,我使用exec sp_executesql @strSql,N'....'的方式来执行是没有问题的,而如果我使用拼接sql的方式,会报错,因为sql字符串被截断了,只截取到了4000个字符长度,即便我把字符串变量长度设置为nvarchar(max)也没用。
分页方式采用Sqlserver2012以上版本才支持的高效方式:offset ... FETCH NEXT ...ROWS ONLY
原来Linq的执行时间测试:


