【发布时间】:2010-08-04 14:58:31
【问题描述】:
针对 SQL Server 2008 使用 NHibernate 2.1.2.4000。目标表没有触发器或无关索引。很简单:
create table LogEntries (
Id INT IDENTITY NOT NULL,
HostName NVARCHAR(32) not null,
UserName NVARCHAR(64) not null,
LogName NVARCHAR(512) not null,
Timestamp DATETIME not null,
Level INT not null,
Thread NVARCHAR(64) not null,
Message NVARCHAR(MAX) not null,
primary key (Id)
)
我的实体映射是:
<class name="LogEntry" table="LogEntries">
<id name="Id" unsaved-value="0">
<generator class="native"/>
</id>
<property name="HostName" length="32" not-null="true"/>
<property name="UserName" length="64" not-null="true"/>
<property name="LogName" length="512" not-null="true"/>
<property name="Timestamp" type="utcdatetime" not-null="true"/>
<property name="Level" not-null="true"/>
<property name="Thread" length="64" not-null="true"/>
<property name="Message">
<column name="Message" sql-type="NVARCHAR(MAX)" not-null="true"/>
</property>
</class>
现在,考虑以下测试用例:
[Fact]
public void bulk_insert_test()
{
var batchSize = 100;
var numberItems = 10000;
var configuration = new NHibernate.Cfg.Configuration().Configure();
configuration.SetProperty("connection.connection_string", @"my_conn_string");
configuration.SetProperty("adonet.batch_size", batchSize.ToString());
var sessionFactory = configuration.BuildSessionFactory();
var ts = this.WriteWithNH(sessionFactory, numberItems);
////var ts = this.WriteWithBC(sessionFactory, numberItems, batchSize);
Console.WriteLine("Saving {0} items with batch size {1}: {2}", numberItems, batchSize, ts);
}
public TimeSpan WriteWithNH(ISessionFactory sessionFactory, int numberItems)
{
using (var session = sessionFactory.OpenStatelessSession())
using (var transaction = session.BeginTransaction())
{
session.Insert(new LogEntry()
{
HostName = "host",
UserName = "user",
LogName = "log",
Level = 0,
Thread = "thread",
Timestamp = DateTime.UtcNow,
Message = "Warm up"
});
transaction.Commit();
}
var sw = Stopwatch.StartNew();
using (var session = sessionFactory.OpenStatelessSession())
using (var transaction = session.BeginTransaction())
{
for (var i = 0; i < numberItems; ++i)
{
session.Insert(new LogEntry()
{
HostName = "host",
UserName = "user",
LogName = "log",
Level = 0,
Thread = "thread",
Timestamp = DateTime.UtcNow,
Message = "Message " + i
});
}
transaction.Commit();
}
return sw.Elapsed;
}
public TimeSpan WriteWithBC(ISessionFactory sessionFactory, int numberItems, int batchSize)
{
using (var session = sessionFactory.OpenStatelessSession())
using (var bulkCopy = new SqlBulkCopy((SqlConnection)session.Connection))
{
bulkCopy.BatchSize = batchSize;
bulkCopy.DestinationTableName = "LogEntries";
var table = new DataTable("LogEntries");
table.Columns.Add("Id", typeof(int));
table.Columns.Add("HostName", typeof(string));
table.Columns.Add("UserName", typeof(string));
table.Columns.Add("LogName", typeof(string));
table.Columns.Add("Timestamp", typeof(DateTime));
table.Columns.Add("Level", typeof(int));
table.Columns.Add("Thread", typeof(string));
table.Columns.Add("Message", typeof(string));
var row = table.NewRow();
row["HostName"] = "host";
row["UserName"] = "user";
row["LogName"] = "log";
row["Timestamp"] = DateTime.UtcNow;
row["Level"] = 0L;
row["Thread"] = "thread";
row["Message"] = "Warm up";
table.Rows.Add(row);
bulkCopy.WriteToServer(table);
}
var sw = Stopwatch.StartNew();
using (var session = sessionFactory.OpenStatelessSession())
using (var bulkCopy = new SqlBulkCopy((SqlConnection)session.Connection))
{
bulkCopy.BatchSize = batchSize;
bulkCopy.DestinationTableName = "LogEntries";
var table = new DataTable("LogEntries");
table.Columns.Add("Id", typeof(int));
table.Columns.Add("HostName", typeof(string));
table.Columns.Add("UserName", typeof(string));
table.Columns.Add("LogName", typeof(string));
table.Columns.Add("Timestamp", typeof(DateTime));
table.Columns.Add("Level", typeof(int));
table.Columns.Add("Thread", typeof(string));
table.Columns.Add("Message", typeof(string));
for (var i = 0; i < numberItems; ++i)
{
var row = table.NewRow();
row["HostName"] = "host";
row["UserName"] = "user";
row["LogName"] = "log";
row["Timestamp"] = DateTime.UtcNow;
row["Level"] = 0;
row["Thread"] = "thread";
row["Message"] = "Message " + i;
table.Rows.Add(row);
}
bulkCopy.WriteToServer(table);
}
return sw.Elapsed;
}
以下是使用 NHibernate 执行插入时的一些示例输出:
Saving 10000 items with batch size 500: 00:00:12.3064936
Saving 10000 items with batch size 100: 00:00:12.3600981
Saving 10000 items with batch size 1: 00:00:12.8102670
作为比较点,您会看到我还实现了一个基于 BCP 的解决方案。这是一些示例输出:
Saving 10000 items with batch size 500: 00:00:00.3142613
Saving 10000 items with batch size 100: 00:00:00.6757417
Saving 10000 items with batch size 1: 00:00:26.2509605
显然,BCP 解决方案比 NH 解决方案快几英里。同样明显的是,批处理会影响 BCP 解决方案的速度,但不会影响 NH 解决方案的速度。使用 NHibernate 进行插入时,NHProf 显示如下:
alt text http://img9.imageshack.us/img9/8407/screenshotac.png
只有INSERTs,没有SELECTs。有趣的是,NHProf 从来没有给我this warning。
我已经尝试按照上面的测试用例在我的配置文件和代码中指定adonet.batch_size。
现在,我不希望 NH 解决方案达到 BCP 解决方案的速度,但我至少想知道为什么批处理不起作用。如果启用批处理功能足够好,那么我可能会在 BCP 上使用 NH 解决方案,以保持代码库更简单。
谁能解释为什么 NH 拒绝支持 ADO.NET 批处理,以及我可以做些什么来解决它?我读过的所有分散的 NH “文档”都指出,您需要做的就是指定 adonet.batch_size 并(最好)使用无状态会话,但我正在做这两件事。
谢谢
【问题讨论】:
-
@Kent:你有没有看过NHProf的幕后情况?你确定插入操作没有被批处理吗?
-
是的,我已经查看了 NHProf,并且在有/没有批处理的情况下运行时没有区别。
-
所以语句没有被分组?一些想法:1)你能发布你的完整配置文件吗? 2)您是否尝试过直接在 Nhibernate 配置文件中指定 Nhibernate 批量大小,而不是在您的测试夹具中覆盖?
-
另外,您的主键是否映射为本机?每次插入操作后,您是否看到每个新 id 的 select 语句?我想知道这是否可能是没有批处理发生的原因?
-
@DanP:我已经更新了我的帖子来回答你所有的问题。
标签: nhibernate sql-server-2008 bulkinsert batching