Warum ist NHibernate Weigerung, die batch-Einsätze?

Verwendung von NHibernate 2.1.2.4000 gegen SQL Server 2008. Die Zieltabelle hat keine Trigger oder überflüssige Indizes. Es ist einfach:

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)
)

Meine entity mapping ist:

<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>

Betrachten Sie nun die folgenden Testfall:

[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;
}

Ist hier einige Beispiel-Ausgabe bei der Verwendung von NHibernate zum ausführen der inserts:

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

Als Ausgangspunkt des Vergleichs, wirst du sehen, dass ich auch umgesetzt BCP-basierte Lösung. Hier einige Beispiel-Ausgabe:

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

Klar, das BCP-Lösung ist meilenweit schneller als der NH ein. Auch klar ist, dass die Dosierung, die die Geschwindigkeit des BCP-Lösung, aber nicht die NH ein. Bei der Verwendung von NHibernate Einfügungen, NHProf zeigt Folgendes:

alt-text http://img9.imageshack.us/img9/8407/screenshotac.png

Gibt es nur INSERTs, kein SELECTs. Interessanterweise an keiner Stelle tut NHProf mir diese Warnung.

Ich habe versucht, die Angabe adonet.batch_size sowohl in meiner config-Datei und im code als pro Testfall oben.

Nun, ich bin mir nicht erwarten, dass die NH-Lösung, um jemals zu erreichen die Geschwindigkeit des BCP-Lösung, aber ich würde zumindest gerne wissen, warum die Dosierung nicht funktioniert. Wenn es gut genug mit der Stapelverarbeitung aktiviert ist, dann kann ich die Verwendung der NH-Lösung über den BCP-nur, um die code-Basis einfacher.

Kann mir jemand erklären, warum NH weigert sich, Sie zu Ehren, ADO.NET Batchverarbeitung, und was kann ich tun, um es zu beheben? Alle die zerstreuten NH "Dokumentation", die ich gelesen habe besagt, dass alle Sie tun müssen ist, geben Sie adonet.batch_size und (bevorzugt) verwenden Sie ein stateless session, aber ich mache diese beiden Dinge.

Dank

  • Haben Sie schon einen Blick auf, was passiert unter der Haube mit NHProf? sind Sie sicher, dass der insert-Operationen werden nicht zusammengefasst?
  • Ja, ich habe mir mit NHProf und es gibt keinen Unterschied zwischen den bei der Ausführung mit/ohne Batchverarbeitung.
  • So die Aussagen sind nicht gebündelt? Ein paar Gedanken: 1) Können Sie Ihre komplette config-Datei? 2) Haben Sie versucht, die Angabe des Nhibernate-batch-Größe direkt in der Nhibernate-config-Datei anstatt überschreiben die in den test-fixture?
  • Auch, ist Ihr Primärschlüssel zugeordnet, die als native? Sehen Sie die select-Anweisung für jede neue id nach jeder insert-operation? Ich Frage mich, ob das könnte die Ursache des no-Dosierung auftreten?
  • Ich habe meinen Beitrag aktualisiert, um alle Ihre Fragen zu beantworten.
  • Wow..das ist ziemlich...unerwartet; ich vermute, Diego hat trifft den Nagel auf den Kopf, aber anscheinend funktioniert das nicht, wie man erwarten würde, wenn über die Identität...

Schreibe einen Kommentar