距離上一篇博文更新已經兩個月過去了。在此,先表一表這兩個月干了些啥:
世界那么大,我也想去看看。四月份的時候,我入職了上海的一家電商公司,職位是.NET高級開發工程師。工作一個月,最大的感受是比以前小城市匆忙了許多,工作壓力大了許多,開發方式更加的正規,不過各種流程也更加的繁雜細瑣。在寫代碼的時候,一定要嚴謹細心,該驗證參數合法性的時候驗參,該拋異常的時候拋異常,該寫日志的時候寫日志,因為一個不小心而報黃頁或者主流程無法順利進行下去,是很沒面子的事情。另外,我也更加關注代碼的性能問題,開發環境和生產環境的數據量根本不是一個數量級的,也許在開發環境頁面加載速度很快一旦上了產線卻慢得無法忍受。
好了,廢話少說,言歸正傳。昨天,Leader安排了一個性能優化的任務。商家要導出訂單信息,如果查詢的訂單時間間隔比較小,沒什么問題,查詢的訂單時間跨度大了,就非常非常非常慢了。項目使用的ORM是LINQ to SQL,至於為什么要使用,這屬於歷史遺留問題了。
有一個訂單明細表,里面有數十個字段,為了簡化問題,我們建一個簡單的實體:
public class OrderItem { public string Id{ get; set; } public string OrderId{ get; set; }//訂單編號 public string ProductId{ get; set; }//商品ID public int ProductQuantity{ get; set; }//商品數量 }
一個方法一個方法排查,最后找到了問題的所在:
List<OrderItem> orderItems=OrderRepository.Find(orderItem=>orderIdList.Contains(orderItem.OrderId)).ToList();
orderIdList是一個List<string>集合,存儲了查詢時間段內的訂單編號。當訂單編號的數量大於50時查詢就開始變慢,大於100時就非常慢了。我在orderIdList里填充了1000個有效的訂單編號,然后用SqlServer Profiler監控,發現上面一句LINQ查詢表達式動態編譯成SQL語句竟然花費了幾十秒的時間,生成的SQL大概如下:
SELECT Id,OrderId,ProductId,ProductQuantity
FROM OrderItems
WHERE OrderId IN (......)
上面的SQL執行時間大約是0.7秒,已經為訂單編號OrderId字段建立了索引,測試數據庫里的數據大約是55萬條。
為了解決上面LINQ查詢表達式動態編譯成SQL耗時比較長的問題,最后決定直接執行SQL語句,並且IN查詢改為JOIN查詢,簡單粗暴。解決方法如下:
//創建臨時表SQL,存儲查詢時間段內的訂單編號 string createSql = "CREATE TABLE #TmpOrderId(OrderId varchar(36));"; //訂單編號插入SQL string orderIds = orderIdList.Aggregate(string.Empty,(current,id) => current + ("('" + id + "'),")); orderIds = orderIds.Remove(orderIds.LastIndexOf(',')); string insertSql = "INSERT INTO #TmpOrderId VALUES " + orderIds + ";"; //JOIN查詢SQL string joinSql = "SELECT T1.Id,T1.OrderId,T1.ProductId,T1.ProductQuantity FROM OrderItems AS T1 JOIN #TmpOrderId AS T2 ON T1.OrderId=T2.OrderId;"; //三條SQL語句要放在一個會話里執行,否則會報找不到臨時表#TmpOrderId異常 IEnumerable<OrderItem> query = DataContext.ExecuteQuery<OrderItem>(createSql + insertSql + joinSql);
最后,采用該解決方法,訂單明細數據基本瞬間就查詢出來了,效果拔群啊,該優化任務圓滿完成。