從剛剛開始接觸ORM到現在已有超過八年時間,用過了不少ORM框架也瞭解了不少ORM框架,看過N種關於ORM框架的相關資料與評論,各種言論讓人很難選擇。在ORM的眾多問題中最突出的問題是關於性能方面的問題,因此我在看了國外的一遍文章(Dapper vs Entity Framework vs ADO. ...
從剛剛開始接觸ORM到現在已有超過八年時間,用過了不少ORM框架也瞭解了不少ORM框架,看過N種關於ORM框架的相關資料與評論,各種言論讓人很難選擇。在ORM的眾多問題中最突出的問題是關於性能方面的問題,因此我在看了國外的一遍文章(Dapper vs Entity Framework vs ADO.NET Performance Benchmarking)後受到啟發,在這個文章的基礎上擴展了測試用例分享給大家。
模型準備
用於測試是模型是基於一個訂單系統,如下圖所示,主要有客戶、產品、倉庫、訂單及訂單明細。其中數據表之前的關係從圖中可以看出,此外產品、訂單明細是自增列,倉庫是兩個主鍵構成的複合主鍵表。雖然只有5張表,但是已經包含了數據表的常用情況。
數據初始化
我們使用以上代碼創建資料庫,初始化數據表結構,生成測試數據,生成數據量如下表所示。
1 static void InitialDatabase() 2 { 3 using (var ef = new Models.EFContext()) 4 { 5 if (!ef.Database.Exists()) 6 { 7 ef.Database.Create(); 8 9 using (var db = new Models.MegoContext()) 10 { 11 db.InitialTable(); 12 } 13 } 14 } 15 using (var db = new Models.MegoContext()) 16 { 17 db.InitialData(); 18 } 19 }
表名 | 說明 | 行數 |
Customers | 客戶表 | 10000 |
Products | 產品表 | 9000 |
Warehouses | 倉庫表 | 30176 |
Orders | 訂單表 | 100000 |
OrderDetails | 訂單明細 | 600271 |
這些數據量已經快接近一個小型系統的數據量了,如果需要到本地執行,請將App.config文件中的連接字元串改成自己的資料庫名直接運行即可。
測試用例說明
目前參與測試的框架如下:
本測試中的例子不僅僅限定於目前指定的這幾種框架,這裡定義了一個介面,如果想加入更多測試框架可以參考代碼自行加入。
1 /// <summary> 2 /// 性能測試項目 3 /// </summary> 4 public interface IPerformanceTest 5 { 6 /// <summary> 7 /// 框架名稱。 8 /// </summary> 9 string Framework { get; } 10 /// <summary> 11 /// 隨機獲取一個客戶 12 /// </summary> 13 /// <param name="id"></param> 14 /// <returns></returns> 15 long GetCustomerById(int id); 16 /// <summary> 17 /// 隨機獲取一個訂單的所有明細 18 /// </summary> 19 /// <param name="orderId"></param> 20 /// <returns></returns> 21 long GetDetailsByOrder(int orderId); 22 /// <summary> 23 /// 隨機獲取一個訂單及所有明細 24 /// </summary> 25 /// <param name="orderId"></param> 26 /// <returns></returns> 27 long GetOrderAndDetails(int orderId); 28 /// <summary> 29 /// 插入離散的N個客戶。 30 /// </summary> 31 /// <returns></returns> 32 long InsertDiscreteCustomers(Customer[] customers); 33 /// <summary> 34 /// 插入離散的N個產品,自增主鍵。 35 /// </summary> 36 /// <returns></returns> 37 long InsertDiscreteProducts(Product[] products); 38 /// <summary> 39 /// 更新離散的N個客戶。 40 /// </summary> 41 /// <returns></returns> 42 long UpdateDiscreteCustomers(Customer[] customers); 43 /// <summary> 44 /// 刪除離散的N個明細。 45 /// </summary> 46 /// <returns></returns> 47 long DeleteDiscreteDetails(OrderDetail[] details); 48 /// <summary> 49 /// 刪除離散的N個倉庫,多主鍵。 50 /// </summary> 51 /// <returns></returns> 52 long DeleteDiscreteWarehouses(Warehouse[] warehouses); 53 }View Code
我們主要有如下表幾項測試(輸出標題用於在結果中顯示):
方法名 | 每次的測試量 | 輸出標題 | 測試說明 |
GetCustomerById | 隨機查詢100次。 | SELECT1 | 隨機用主鍵獲取指定客戶數據。 |
GetDetailsByOrder | 隨機查詢100次。 | SELECT2 | 隨機用訂單主鍵獲取相關的訂單明細數據。 |
GetOrderAndDetails | 隨機查詢100次。 | SELECT3 | 隨機用訂單主鍵獲取當前訂單及所有訂單明細數據。 |
InsertDiscreteCustomers | 插入500條數據。 | INSERT1 | 插入指定數量的客戶。 |
InsertDiscreteProducts | 插入500條數據。 | INSERT2 | 插入指定數量的產品(產品的主鍵是自增列的)。 |
UpdateDiscreteCustomers | 更新500條數據。 | UPDATE | 更新指定數量的客戶。 |
DeleteDiscreteDetails | 刪除500條數據。 | DELETE1 | 刪除指定數量的訂單明細。 |
DeleteDiscreteWarehouses | 刪除500條數據。 | DELETE2 | 刪除指定數量的倉庫(倉庫是複合主鍵)。 |
這裡我們已經測試一個框架的增刪改查,單個主鍵、複合主鍵、自增主鍵都有覆蓋。為了公平我們將按順序執行每個框架的測試,然後再重覆執行多次,以下為測試運行代碼,我們將忽略第一輪的運行結果。
List<TestResultItem> results = new List<TestResultItem>(); for (int i = 0; i < TestSumCount + 1; i++) { foreach (var framework in frameworks) { foreach (var p in insertProducts) p.Id = 0; var item = new TestResultItem(framework) { Convert.ToInt64(Enumerable.Range(0,TestSelectCount1).Sum(a=> framework.GetCustomerById(r.Next(customeIds.Item1, customeIds.Item2)) )), Convert.ToInt64(Enumerable.Range(0,TestSelectCount2).Sum(a=> framework.GetDetailsByOrder(r.Next(orderIds.Item1, orderIds.Item2)) )), Convert.ToInt64(Enumerable.Range(0,TestSelectCount3).Sum(a=> framework.GetOrderAndDetails(r.Next(orderIds.Item1, orderIds.Item2)) )), framework.InsertDiscreteCustomers(insertCustomes), framework.InsertDiscreteProducts(insertProducts), framework.UpdateDiscreteCustomers(updateCustomes), framework.DeleteDiscreteDetails(deleteDetails), framework.DeleteDiscreteWarehouses(deleteWarehouse), }; if (i > 0) { results.Add(item); } } }View Code
測試結果
如下圖所示為測試的結果,本次測試針對 每個框架運行了4次,結果中首先輸出了每個框架每一次的運行結果,在最後的彙總中輸出了所有框架的平均用時。
結果分析
首先我們需要強調的是以上測試忽略了一些先進ORM框架的優勢,例如批量插入自增列數據時,所生成的編號會返回到原對象中。從如上圖的測試結果可以看出在查詢方面各個框架都是比較接近的,不過會隨著數據量升高這個差異會升高,在插入、更新及刪除操作中就差別比較大了。主要原因是在批量提交中各個框架是逐個語句提交,還是組合批量提交的差別,這裡主要是實現SQL語句的不同所產生的差異。在查詢方面ADO.NET+AutoMapper是性能最高的,這個沒有爭議,如果有疑問可以參考開頭所發的例子中,本文沒有測試這個項目是因為修改操作實現不理想。
這裡需要補充一下,從上面的測試結果來看SqlSugar的插入和刪除比其他框架要高很多,其實這是有安全代價的,因為該框架是直接將值生成在SQL語句中的如下圖所示,所以使用者需要在SQL註入方面註意一下。
後計
本文只把測試結果顯示出來,沒有結出任何結論,有興趣的朋友可以從 Github 上下載代碼來運行查看結果,後續還會持續更新加入更多框架進行測試。