C#大型電商項目優化(二)——嫌棄EF與拋棄EF


上一篇博文中講述了使用EF開發電商項目的代碼基礎篇,提到EF后,一語激起千層浪。不少園友紛紛表示:EF不適合增長速度飛快的互聯網項目,EF只適合企業級應用等等。

也有部分高手提到了分布式,確實,性能優化從數據庫出發,初期就加索引,然后垂直拆分,水平拆分,讀寫分離,甚至是分布式事務,陽春白雪,格局很高。然而筆者希望通過漸進的過程來優化這個項目,我們縮小格局,從細節查看不同方案的優劣。

之前提過,使用EF最主要的原因是項目時間緊迫,EF搭建速度快,熟悉的同事也多,使用方便。這個決策確實幫助我們挺過了初期的難關。在業務量增長的過程中,一些問題也逐漸暴露出來,我們開始針對問題做優化。

問題1:部分請求響應緩慢,影響用戶體驗。

使用EF做數據的增刪改查,一些不規范代碼也會拖慢程序效率,筆者在上一篇中已經提過。某些請求中可能包含多次數據查詢與更新,如果這些細小的問題都以低效運行,那這個請求確實會很慢。然而在EF的框架下優化它,也未必能收到明顯成效。以更新商品銷量為例,我們上方案與代碼:

方案1

先查出某一條數據,然后填入新算出的銷量,再更新數據庫,代碼如下:

  //先查出數據,再更新
            var productSKU = unitOfWork.ProductSku.GetByID(dtos[0].Items[0].SKUId);
            productSKU.SalesCount = productSKU.SalesCount + dtos[0].Items[0].Quantity;
            unitOfWork.ProductSku.Update(productSKU);
            unitOfWork.Submit();

其響應時間如圖:

方案2

采用IQuryable,之前提到過,這種查詢方式不會真的將全部數據加載到內存,代碼如下:

//2采用IQueryable查詢更新
            var productSKU1 = unitOfWork.ProductSku.Get(p => p.Id == dtos[0].Items[0].SKUId);
            foreach (var item in productSKU1)
            {
                item.SalesCount = item.SalesCount + dtos[0].Items[0].Quantity;
                unitOfWork.ProductSku.Update(item);
            }
            unitOfWork.Submit();

其響應時間如圖:

方案3

直接使用SQL,簡單粗暴,代碼如下:

//3直接使用sql更新
            string updateSql = @"update ProductSKU set SalesCount=SalesCount+" + dtos[0].Items[0].Quantity + " where Id='" + dtos[0].Items[0].SKUId.ToString() + "'";
            unitOfWork.ProductSku.ExecuteSqlCommand(updateSql);
            unitOfWork.Submit();

其響應時間如圖:

 

我們來分析下這三種方案:

方案1簡單易懂,將數據查出來,更新后再塞回數據庫,邏輯清晰,代碼更清晰。可是將數據取出來,加載到內存,再對內存里的數據進行修改,最后生成SQL送回數據庫執行,這套走下來,好像兜了一大圈,其實我們只想更新個銷量。

方案2顯得好了些,使用了我們在前一篇中講到的理念,不將數據整個加載到內存,只到用時才加載,通過這種方式更新,其實生成的SQL和直接執行SQL差不太多了,但是我們可以發現:方案2和方案1時間差不多。在筆者預期中,方案2應該是比方案1好的,至於時間差不多的原因,應該是方案2用了一次循環,第二次循環時不符合條件,然后跳出循環,造成了些許時間浪費。如果是批量更新銷量,方案2必然比方案1優秀很多。

方案3自然是簡單粗暴,一條SQL搞定,毫不拖泥帶水,其效果也是喜人的,幾乎比方案1快1倍!

之前有園友提到索引的問題,其實我廠有專職DBA,不僅為數據量較大的表添加了聚集索引和非聚集索引,還寫了定時任務去更新索引。所以索引這塊我們不深究。

早起追逐開發效率階段,我們可能將方案1優化為方案2,然后繼續做新功能開發。但是現在我們有更多的選擇,我們也有更多的時間去深究用戶體驗與系統效率了,那么自然的,我們開始嫌棄EF了。但是開發到這個程度,再去更換框架似乎不合適。而且大道至簡,如果能用SQL搞定,那必是極好的。於是乎,我們開始對關鍵部分業務代碼做重構,替換為原生SQL。這似乎是拋棄EF的開端。

 

問題2:部分數據量很大的表需要分表,EF難以維系

EF是ORM框架,映射數據庫對象,然而同一數據庫的一張表被拆分為兩張以上,EF似乎沒法映射了,兩張表字段完全一致,難道再寫個Model?而且分表是個動態的過程,也許一年分一次,也許一個月分一次,而且可能是定時任務去執行的,總不能一分表就改代碼。自此,我們和EF的矛盾激化了。

園子里有關於數據庫拆分的博客,我們所要改的只有數據訪問層,最好不要動業務層。而且我們上文也提到:用原生SQL。那么我們就用原生SQL重寫數據訪問。

這里舉個例子,比如我們拆分訂單表,按年拆分,通常用戶只會查看當年的訂單,所以主表的查詢次數肯定比其他分出的表要多,如果有用戶要查往年的訂單,我們再將查詢范圍擴大。按照這樣的理念,我們開始重寫數據訪問層,並按照以下要點執行:

1.使用原生SQL

2.添加日期參數,如果日期超出主表的范圍,則開始連接查詢

3.查詢中也可以添加其他條件,將參數作為對象填入SQL

4.抽象出一個生成SQL的公共方法,方便大家調用

拼裝SQL是一件極其考驗基本功的事,這個公共方法是我廠一位大師級的數據專家抽象出來的,大家也可以按照這個思路嘗試下,一個簡化版是很容易做出來的。

至此,項目中重要的業務功能已經和EF脫離關系了,我們也欣喜地收獲了SQL帶來的效率。而其他功能模塊中,沒有高並發場景或並不常用的應用,我們並沒有做重構。

盡管本文標題是嫌棄,但如果是企業級應用,需要兼顧開發效率,且沒有互聯網模式下的業務量激增狀況,筆者仍然推薦使用EF。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM