ORM性能测试Benchmark(最终版)

ORM性能测试Benchmark(最终版)

本测试聚焦 ORM 在查询过程中,对查询表达式解析、数据映射、流程处理的性能差异。
由于 SQL 的实际执行由数据库引擎负责,ORM 无法改变数据库层面的执行逻辑;不同 ORM 的差异主要体现在 SQL 拼接、表达式解析和数据映射等实现细节(例如插入操作可通过生成 SQL 或使用 BulkCopy 实现)。
因此,本测试不对实现方式完全不同的操作(如 BulkCopy)进行比较,而是重点衡量表达式解析与数据映射两方面的运行效率与内存占用。

测试声明

本测试不代表任何立场和原作者也没任何关系,仅是在研究、学习、优化、测试,对内部项目myTest整理过程中形成的测试,有其它测式可下载源码自行添加实现。

测试环境

项目 说明
测试框架 BenchmarkDotNet
测试数据库 SQLite(单机性能优,波动较小)
.NET 版本 .NET 6.0+
测试硬件 Intel Core i5-8265U CPU 1.60GHz

测试的ORM

下面列出了近年收集到的ORM,除Dapper外,未涉及到表达式解析的ORM没有加入此测试

<PackageReference Include="Chloe.SQLite" Version="5.55.0" />
    <PackageReference Include="Dapper" Version="2.1.66" />
    <PackageReference Include="FreeSql.Provider.Sqlite" Version="3.5.305" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.0-preview.6.23329.4" />
    <PackageReference Include="SqlSugarCore" Version="5.1.4.212-preview02" />
    <PackageReference Include="linq2db" Version="6.2.0" />
    <Reference Include="Fast.Framework">

具体测试项目如下

  1. 对表达式解析进行测试 testQueryCondition
  2. 对查询返回结果进行数据映射测试 testQueryResult
  3. 自定义数据映射测试 testQueryAnonymousResult
  4. 循环读取指定数据测试 testQueryLoop

测试代码明细

对表达式解析进行测试 testQueryCondition

指定了查询条件和返回结果筛选
这里可以反映表达式解析生成SQL效率,ORM技术核心最困难的部份,非常考验代码组织和逻辑

query.Where(b => b.F_String == "111" && b.F_Decimal > 0 && b.F_Bool == true && b.F_String.StartsWith("abc")).Select(b => new { b.F_Float, b.F_Bool, b.F_Double, b.F_Byte, b.F_String, b.F_Decimal, b.F_Int64 }).ToSqlString();

对查询返回结果进行数据映射测试 testQueryResult

返回指定的数量的数据进行数据映射,以测试映射效率和内存使用情况

query.Take(100).ToList();

自定义数据映射测试 testQueryAnonymousResult

指定数据映射的结构,以测试解析和映射效率、内存使用情况
这里使用了匿名对象,是一个比较特殊的处理

query.Take(100).Select(b => new
           {
               b.Id,
               b.F_Float,
               b.F_Bool,
               b.F_DateTime,
               b.F_Decimal,
               b.F_Double,
               b.F_Int64
           }).ToList();

循环读取指定数据测试 testQueryLoop

循环多次查询,以测试查询和映射效率
这里验证数据连接效率和查询效率,虽然每次查询的数据量很小,但循环多次会放大差异,能更明显的看出差别

for (var i = 0; i < 20; i++)
   {
       var item = query.Where(b => b.Id == i).ToList();
   }

Benchmark测试代码样例

[MemoryDiagnoser]
   public class ConditionTest : TestBase
   {
       [Benchmark]
       public void TestCondition()
       {
           Invoke(b => b.testQueryCondition());
       }
   }

以下是具体测试结果,仅供参考

表格字段说明:

  • Mean: 所有测量值的算术平均值 ns纳秒 μs微秒 ms毫秒。
  • Error: 99.9% 置信区间的一半。
  • StdDev: 所有测量值的标准差。
  • Gen0: 第 0 代 GC 每 1000 次操作收集一次。
  • Gen1: 第 1 代 GC 每 1000 次操作收集一次。
  • Gen2: 第 2 代 GC 每 1000 次操作收集一次。
  • Allocated: 每次操作分配的内存(仅托管内存,包含所有内容,1KB = 1024B)。

TestCondition

Dapper由于没有表达式解析,空跑,最低和最高不管是效率和内存占用差了一数量级
提示:拼接SQL会导致注入风险,除了语法字符外还有字符编码问题

Method ProvideType Mean Error StdDev Gen0 Gen1 Allocated
TestCondition ChloeTest 90.75 μs 1.020 μs 0.954 μs 5.3711 - 16.68 KB
TestCondition EfSqlliteTest 217.38 μs 2.950 μs 2.615 μs 19.5313 - 61.19 KB
TestCondition FastFrameworkTest 94.90 μs 0.807 μs 0.630 μs 6.7139 - 20.71 KB
TestCondition FreeSqlTest 779.56 μs 12.514 μs 11.706 μs 9.7656 3.9063 62.56 KB
TestCondition LinqToDbTest 103.50 μs 2.002 μs 2.056 μs 6.1035 - 18.79 KB
TestCondition MyTest 43.00 μs 0.505 μs 0.395 μs 4.8218 - 14.95 KB
TestCondition SqlSugarTest 366.82 μs 7.205 μs 6.016 μs 33.6914 - 103.83 KB

TestResult

强类型直接转换,最低和最高差别两倍,EF效率比大部份好

Method ProvideType Mean Error StdDev Gen0 Gen1 Allocated
TestResult ChloeTest 582.8 μs 10.02 μs 8.37 μs 22.4609 - 70.68 KB
TestResult DapperTest 500.8 μs 3.14 μs 2.94 μs 16.6016 - 52.68 KB
TestResult EfSqlliteTest 1,127.4 μs 15.77 μs 14.75 μs 64.4531 - 202.9 KB
TestResult FastFrameworkTest 11,561.9 μs 119.70 μs 93.46 μs 46.8750 - 153.21 KB
TestResult FreeSqlTest 1,300.0 μs 24.25 μs 22.68 μs 31.2500 11.7188 96.32 KB
TestResult LinqToDbTest 1,273.8 μs 20.71 μs 18.36 μs 17.5781 - 54.77 KB
TestResult MyTest 600.5 μs 11.94 μs 11.16 μs 15.6250 - 48.34 KB
TestResult SqlSugarTest 1,408.5 μs 27.42 μs 38.44 μs 46.8750 - 147.57 KB

TestAnonymousResult

Dapper由于没有结果筛选,同TestResult(SqlSugar内存溢出?)

Method ProvideType Mean Error StdDev Gen0 Gen1 Allocated
TestAnonymousResult ChloeTest 552.7 μs 5.48 μs 4.86 μs 20.5078 - 65.54 KB
TestAnonymousResult DapperTest 484.2 μs 4.71 μs 4.18 μs 17.5781 - 55.8 KB
TestAnonymousResult EfSqlliteTest 692.2 μs 11.48 μs 10.74 μs 33.2031 - 103.18 KB
TestAnonymousResult FastFrameworkTest 7,838.4 μs 67.26 μs 52.51 μs 31.2500 - 122.4 KB
TestAnonymousResult FreeSqlTest 1,940.9 μs 20.24 μs 18.93 μs 70.3125 11.7188 225.84 KB
TestAnonymousResult LinqToDbTest 1,241.1 μs 18.58 μs 14.51 μs 15.6250 - 53.21 KB
TestAnonymousResult MyTest 473.4 μs 4.67 μs 4.14 μs 12.6953 - 39.89 KB
TestAnonymousResult SqlSugarTest 8,667.7 μs 133.08 μs 111.13 μs 1046.8750 - 3210.65 KB

TestQueryLoop

循环多次查询调用,由于调用sqlite驱动不同,时间差别较大,但从内存使用上能看出差别

Method ProvideType Mean Error StdDev Gen0 Gen1 Allocated
TestQueryLoop ChloeTest 2.535 ms 0.0427 ms 0.0379 ms 74.2188 - 231.78 KB
TestQueryLoop DapperTest 1.433 ms 0.0274 ms 0.0243 ms 17.5781 - 54.7 KB
TestQueryLoop EfSqlliteTest 5.410 ms 0.0697 ms 0.0618 ms 375.0000 - 1156.31 KB
TestQueryLoop FastFrameworkTest 230.424 ms 4.5239 ms 4.0103 ms 666.6667 - 2332.27 KB
TestQueryLoop FreeSqlTest 16.176 ms 0.3208 ms 0.6257 ms 93.7500 31.2500 664.93 KB
TestQueryLoop LinqToDbTest 16.881 ms 0.2541 ms 0.2252 ms 125.0000 - 406.34 KB
TestQueryLoop MyTest 2.058 ms 0.0405 ms 0.0379 ms 46.8750 - 148.13 KB
TestQueryLoop SqlSugarTest 4.210 ms 0.0739 ms 0.0655 ms 210.9375 - 651.17 KB

引入不同数据库实现的方式

本次测试使用的sqlite数据库,各种引入的方式也不相司,大致分为三类:

  1. 引用相关数据库的项目扩展包(ORM主体+扩展+数据库驱动)
  2. 直接包含所有支持的数据库驱动(ORM主体+数据库驱动*N)
  3. 按需自动或手动配置引入的数据库驱动(ORM主体+按需数据库驱动)

对于第一种,多数项目都采用这种方式,缺点是需要引入多个包,增加了依赖关系,并且强绑定了数据库驱动
对于第二种,虽然可以支持多种数据库,但会增加项目的体积,并且可能引入不必要的依赖,也强绑定了数据库驱动
对于第三种,封装度最高,虽然可以按需引入数据库驱动,减少了项目的体积和依赖复杂度,但由于没有强依赖,部份实现可能由于驱动不一致而实现复杂,如 BulkCopy

以LinqToDb为例,直接引入LinqToDb包,手动配置连接串 new DataOptions().UseConnectionString(ProviderName.SQLite, ITest.sqlLiteDb) 它就能自别识别当前项目引入的sqlite驱动,并且对特殊方法也进行了封装(BulkCopy),不管是哪种数据库,引入驱动后所有方法行为一至,无其它依赖。

各种测试项目的引入方式如下表所示:

ProvideType 引入方式
ChloeTest 扩展包&手动配置
DapperTest 扩展包
EfSqlliteTest 扩展包
FastFrameworkTest 手动配置
FreeSqlTest 扩展包
MyTest 手动或自动配置
SqlSugarTest 全包含
LinqToDbTest 自动配置

如何使用此测试

  1. 使用Release发布此项目
  2. 运行dbTest.exe
  3. 输入序号选择需要运行测试的方法,示例如下
---------------------[ Program ]---------------------
[1]  testQueryResult               [2]  testQueryAnonymousResult
[3]  testQueryCondition            [4]  testQueryLoop
[5]  testMethod
---------------------[ invokeAll ]---------------------
[6]  invokeAll
invoke method:
  1. 输入序号后回车,等待测试完成,测试结果会输出到控制台,并在运行目录生成BenchmarkDotNet.Artifacts文件夹,里面有详细的测试结果和分析报告
  2. 运行效果截图
    dbTest.gif

测试项目代码

https://gitee.com/hubro/dbTest.git

参考

使用 BenchmarkDotNet 对 .NET 代码进行性能基准测试
https://cloud.tencent.com/developer/article/2483382
mysql注入-字符编码技巧
https://developer.aliyun.com/article/1658273

posted @ 2026-03-24 14:00  hubro  阅读(124)  评论(2)    收藏  举报