从刚刚开始接触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 上下载代码来运行查看结果,后续还会持续更新加入更多框架进行测试。