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 は次のように表示します。
代替テキスト http://img9.imageshack.us/img9/8407/screenshotac.png
sのみあり、INSERT
s はありませんSELECT
。興味深いことに、NHProf でこの警告が表示されることはありません。
adonet.batch_size
上記のテストケースに従って、構成ファイルとコードの両方で指定しようとしました。
現在、NH ソリューションが BCP ソリューションの速度に達するとは思っていませんが、少なくともバッチ処理が機能しない理由を知りたいです。バッチ処理を有効にしても問題がなければ、コード ベースを単純にするためだけに、BCP ではなく NH ソリューションを使用できます。
NH が ADO.NET のバッチ処理を拒否する理由と、それを修正するために私にできることを誰か説明できますか? 私が読んだ散在するNHの「ドキュメント」はすべてadonet.batch_size
、ステートレスセッションを指定して(できれば)使用するだけでよいと述べていますが、私はそれらの両方を行っています。
ありがとう