NHibernate的大数据量插入的简单性能测试
很多人对于ORM的性能有偏见, 认为其性能会很差. 当然在大部分情况下, 查询的性能可以根据检索策略, 延迟加载, 二级缓存等等进行调优, 这个我就不谈了, 因为有大量的文章涉及到这点. 我这篇文章, 主要是想探讨另外一个方面的性能问题, 大数据量批量操作方面的.
对于一般有经验的ORM使用者来说, 一提到大数据量操作一般也就会说, 嗯, 这个嘛, ORM对此确实效率低下, 我们一般采用直接ADO.net. 确实是这样的. 如果是批量删除. 用ORM去做绝对是效率低下的. 那么批量插入呢? 我这边做了一个测试. 来证明使用ORM并对其进行性能调优, 有可能比直接使用SQL速度更快.
我定义了一个简单的实体, 只有两个属性, 一个是ID, 一个是Description. ID是GUID类型. 我没有使用NH的配置文件对其进行映射, 改为使用Castle AR的attribute. 其他方面均和NH一致. 另外我定义了一个UnitOfWork的类, 用来管理session这些东西. 大家可以忽略.
我这边只测试了插入60000条数据. 分别使用了纯粹的ADO.net, 默认情况下的NH, 以及设置了Batch_Size的NH, 最后是NH2里面最新引入的StatelessSession.
测试的结果可能会给大家一个惊奇. 我的机器是 T61p, 配置为 CPUT7250, 2G RAM. 不同机器时间可能不一样.
ADO.NET: 00:00:12.1994805
NH: 00:00:20.0452743
NH With Batch_Size 100: 00:00:08.8320574
NH With StatelessSession: 00:00:04.8881699
可以看到. 在默认情况下, NH插入这样简单的一个对象, 会比ADO.NET慢上许多. 耗时快2倍了. 的确很慢. 但是如果设置Batch_size为100之后, 耗时居然只有直接ADO.net的2/3. 如果使用了StatelessSession, 更是只有1/3左右.
当然, 你可能会说, 我写SQL也可以进行优化, 也可以100条sql一起执行, 这样性能也会大大提高. 但是. 我们可以看到. NH的性能优化是非注入性的, 只需要调整一下配置文件即可. 而不需要对代码进行修改. 几乎所有SQL可以调优的方法, ORM基本都可以使用, 但是相对于SQL, ORM还有更多更高层次的调优方法.
自此, 所谓NH的性能比直接ADO.NET慢的说法也不攻自破.
[ActiveRecord]
public class GuidTestObject
{
[PrimaryKey(PrimaryKeyType.GuidComb)]
public virtual Guid Id { get; set; }
[Property]
public virtual string Description { get; set; }
}
[TestFixture]
public class PerformanceTest : DatabaseTestFixtureBase
{
[SetUp]
public void TestInitialize()
{
InitializeFramework();
}
[TearDown]
public void TestCleanup()
{
DisposeUnitOfWork();
}
private IList<GuidTestObject> CreateObjects(int count)
{
IList<GuidTestObject> list = new List<GuidTestObject>(count);
for (int i = 0; i < count; i++)
{
list.Add(new GuidTestObject{Description = Guid.NewGuid().ToString()});
}
return list;
}
[Test]
[TestCategory("Performance")]
public void Insert60000DataWithADO()
{
// My result is 00:00:12.1994805 on a T7250, 2g laptop machine.
var watch = new Stopwatch();
watch.Start();
using (var transaction = UnitOfWork.Current.BeginTransaction())
{
for (int i = 0; i < 60000; i++)
{
Repository<GuidTestObject>.ExecuteNoneQuery("insert into GuidTestObjects values(@Id, @Description)",
new[]
{
new Parameter("Id", Guid.NewGuid()),
new Parameter("Description",
Guid.NewGuid().ToString())
});
}
transaction.Commit();
}
watch.Stop();
Console.WriteLine(watch.Elapsed);
}
[Test]
[TestCategory("Performance")]
public void Insert60000DataWithNHBatchSize1()
{
// My result is 00:00:20.0452743 on a T7250, 2g laptop machine. show_sql=false, adonet.batch_size=1
var objects = CreateObjects(60000);
var watch = new Stopwatch();
watch.Start();
using (var transaction = UnitOfWork.Current.BeginTransaction())
{
foreach (var o in objects)
{
Repository<GuidTestObject>.Save(o);
}
transaction.Commit();
}
watch.Stop();
Console.WriteLine(watch.Elapsed);
}
[Test]
[TestCategory("Performance")]
public void Insert60000DataWithNHBatchSize100()
{
// My result is 00:00:08.8320574 on a T7250, 2g laptop machine. show_sql=false, adonet.batch_size=100
var objects = CreateObjects(60000);
var watch = new Stopwatch();
watch.Start();
using (var transaction = UnitOfWork.Current.BeginTransaction())
{
foreach (var o in objects)
{
Repository<GuidTestObject>.Save(o);
}
transaction.Commit();
}
watch.Stop();
Console.WriteLine(watch.Elapsed);
}
[Test]
[TestCategory("Performance")]
public void Insert60000DataWithNHWithStatelessSession()
{
// My result is 00:00:04.8881699 on a T7250, 2g laptop machine. show_sql=false, adonet.batch_size=100
var objects = CreateObjects(60000);
var watch = new Stopwatch();
watch.Start();
using (var session = UnitOfWork.GetSessionFactoryFor(typeof(GuidTestObject)).OpenStatelessSession())
{
using (var transaction = session.BeginTransaction())
{
foreach (var o in objects)
{
session.Insert(o);
}
transaction.Commit();
}
}
watch.Stop();
Console.WriteLine(watch.Elapsed);
}
}