6

NHibernate を使用して PostgreSQL データベースとやり取りしています。

バックグラウンド

簡単なテストをいくつか行いました... 300 レコードを保持するのに 2 秒かかるようです。私は同じ機能を持つ Perl プログラムを持っていますが、代わりに直接 SQL を発行すると、時間の 70% しかかかりません。これが予想されるかどうかはわかりません。私は、C#/NHibernate の方が高速であるか、少なくとも同等であると考えていました。

質問

私の観察の 1 つは、(show_sqlオンにすると)、NHibernate は、複数の行を処理する一括 INSERT を実行する代わりに、INSERT を数百回発行していることです。また、「ネイティブ」ジェネレーターを使用せずに、自分で主キーを割り当てていることに注意してください。

それは期待されていますか?とにかく、代わりに一括 INSERT ステートメントを発行させることはできますか? これは、パフォーマンスを高速化できる領域の 1 つになる可能性があるように思えます。

4

2 に答える 2

6

stachu が正しく見つけたように: NHibernate には PostgreSQL(Npgsql) 用の *BatchingBatcher(Factory) がありません

私は、Npgsql のバッチ処理を一切使用しないが、SQL 文字列「オールドスクール スタイル」を操作する Batcher を作成しました (INSERT INTO [..] VALUES (...),(...), ...)

using System;
using System.Collections;
using System.Data;
using System.Diagnostics;
using System.Text;
using Npgsql;

namespace NHibernate.AdoNet
{
    public class PostgresClientBatchingBatcherFactory : IBatcherFactory
    {
        public virtual IBatcher CreateBatcher(ConnectionManager connectionManager, IInterceptor interceptor)
        {
            return new PostgresClientBatchingBatcher(connectionManager, interceptor);
        }
    }

    /// <summary>
    /// Summary description for PostgresClientBatchingBatcher.
    /// </summary>
    public class PostgresClientBatchingBatcher : AbstractBatcher
    {

        private int batchSize;
        private int countOfCommands = 0;
        private int totalExpectedRowsAffected;
        private StringBuilder sbBatchCommand;
        private int m_ParameterCounter;

        private IDbCommand currentBatch;

        public PostgresClientBatchingBatcher(ConnectionManager connectionManager, IInterceptor interceptor)
            : base(connectionManager, interceptor)
        {
            batchSize = Factory.Settings.AdoBatchSize;
        }


        private string NextParam()
        {
            return ":p" + m_ParameterCounter++;
        }

        public override void AddToBatch(IExpectation expectation)
        {
            if(expectation.CanBeBatched && !(CurrentCommand.CommandText.StartsWith("INSERT INTO") && CurrentCommand.CommandText.Contains("VALUES")))
            {
                //NonBatching behavior
                IDbCommand cmd = CurrentCommand;
                LogCommand(CurrentCommand);
                int rowCount = ExecuteNonQuery(cmd);
                expectation.VerifyOutcomeNonBatched(rowCount, cmd);
                currentBatch = null;
                return;
            }

            totalExpectedRowsAffected += expectation.ExpectedRowCount;
            log.Info("Adding to batch");


            int len = CurrentCommand.CommandText.Length;
            int idx = CurrentCommand.CommandText.IndexOf("VALUES");
            int endidx = idx + "VALUES".Length + 2;

            if (currentBatch == null)
            {
                // begin new batch. 
                currentBatch = new NpgsqlCommand();   
                sbBatchCommand = new StringBuilder();
                m_ParameterCounter = 0;

                string preCommand = CurrentCommand.CommandText.Substring(0, endidx);
                sbBatchCommand.Append(preCommand);
            }
            else
            {
                //only append Values
                sbBatchCommand.Append(", (");
            }

            //append values from CurrentCommand to sbBatchCommand
            string values = CurrentCommand.CommandText.Substring(endidx, len - endidx - 1);
            //get all values
            string[] split = values.Split(',');

            ArrayList paramName = new ArrayList(split.Length);
            for (int i = 0; i < split.Length; i++ )
            {
                if (i != 0)
                    sbBatchCommand.Append(", ");

                string param = null;
                if (split[i].StartsWith(":"))   //first named parameter
                {
                    param = NextParam();
                    paramName.Add(param);
                }
                else if(split[i].StartsWith(" :")) //other named parameter
                {
                    param = NextParam();
                    paramName.Add(param);
                }
                else if (split[i].StartsWith(" "))  //other fix parameter
                {
                    param = split[i].Substring(1, split[i].Length-1);
                }
                else
                {
                    param = split[i];   //first fix parameter
                }

                sbBatchCommand.Append(param);
            }
            sbBatchCommand.Append(")");

            //rename & copy parameters from CurrentCommand to currentBatch
            int iParam = 0;
            foreach (NpgsqlParameter param in CurrentCommand.Parameters)
            {
                param.ParameterName = (string)paramName[iParam++];

                NpgsqlParameter newParam = /*Clone()*/new NpgsqlParameter(param.ParameterName, param.NpgsqlDbType, param.Size, param.SourceColumn, param.Direction, param.IsNullable, param.Precision, param.Scale, param.SourceVersion, param.Value);
                currentBatch.Parameters.Add(newParam);
            }

            countOfCommands++;
            //check for flush
            if (countOfCommands >= batchSize)
            {
                DoExecuteBatch(currentBatch);
            }
        }

        protected override void DoExecuteBatch(IDbCommand ps)
        {
            if (currentBatch != null)
            {
                //Batch command now needs its terminator
                sbBatchCommand.Append(";");

                countOfCommands = 0;

                log.Info("Executing batch");
                CheckReaders();

                //set prepared batchCommandText
                string commandText = sbBatchCommand.ToString();
                currentBatch.CommandText = commandText;

                LogCommand(currentBatch);

                Prepare(currentBatch);

                int rowsAffected = 0;
                try
                {
                    rowsAffected = currentBatch.ExecuteNonQuery();
                }
                catch (Exception e)
                {
                    if(Debugger.IsAttached)
                        Debugger.Break();
                    throw;
                }

                Expectations.VerifyOutcomeBatched(totalExpectedRowsAffected, rowsAffected);

                totalExpectedRowsAffected = 0;
                currentBatch = null;
                sbBatchCommand = null;
                m_ParameterCounter = 0;
            }
        }

        protected override int CountOfStatementsInCurrentBatch
        {
            get { return countOfCommands; }
        }

        public override int BatchSize
        {
            get { return batchSize; }
            set { batchSize = value; }
        }
    }
}
于 2011-09-23T09:22:30.150 に答える
2

また、NHibernateがPostgreSQLへのバッチ挿入を行っていないこともわかりました。私は2つの考えられる理由を特定しました:

1)Npgsqlドライバーはバッチ挿入/更新をサポートしていません(フォーラムを参照

2)NHibernateにはPostgreSQL(Npgsql)用の* BatchingBatcher(Factory)がありません。NHibernateでDevartdotConnectドライバーを使用してみました(NHibernate用のカスタムドライバーを作成しました)が、それでも機能しませんでした。

このドライバーはIEmbeddedBatcherFactoryProviderインターフェースも実装する必要があると思いますが、私にとっては些細なことではないようです(Oracle用のドライバーを使用しても機能しませんでした;))

誰かがNhibarnateにPostgreSQLへのバッチ挿入を強制することができましたか、それとも私の結論を確認できましたか?

于 2011-04-06T09:54:58.790 に答える