NHibernate, похоже, не выполняет массовую вставку в PostgreSQL.

Я взаимодействую с базой данных PostgreSQL с помощью NHibernate.

Фон

Я сделал несколько простых тестов... кажется, сохранение 300 записей занимает 2 секунды. У меня есть программа на Perl с идентичной функциональностью, но вместо этого прямой SQL занимает всего 70% времени. Я не уверен, ожидается ли это. Я думал, что C#/NHibernate будет быстрее или, по крайней мере, на одном уровне.

Вопросы

Одно из моих наблюдений заключается в том, что (с включенным show_sql) NHibernate выдает INSERT несколько сотен раз вместо массового INSERT, который заботится о нескольких строках. И обратите внимание, я сам назначаю первичный ключ, а не использую «родной» генератор.

Это ожидается? Могу ли я в любом случае заставить его вместо этого выдавать массовый оператор INSERT? Мне кажется, что это может быть одна из областей, где я мог бы ускорить работу.


person HelloSam    schedule 06.01.2011    source источник
comment
Если вы сможете убедить nhibernate использовать copy from вместо insert, он, скорее всего, будет работать на порядок быстрее. Это может быть то, что делает программа perl.   -  person nate c    schedule 06.01.2011


Ответы (2)


Как stachu правильно выяснил: NHibernate не имеет * BatchingBatcher (Factory) для PostgreSQL (Npgsql). Как stachu спрашивает: удалось ли кому-нибудь заставить Nhibarnate выполнять пакетные вставки в PostgreSQL?

Я написал Batcher, который не использует пакетную обработку Npgsql, но манипулирует строкой SQL в «старом школьном стиле» (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; }
        }
    }
}
person Gerrit    schedule 23.09.2011
comment
Я должен упомянуть, что вам нужно будет установить свойство adonet.factory_class в конфигурации NHibernate на полное имя класса PostgresClientBatchingBatcherFactory и, конечно же, установить adonet.batch_size на число больше, чем 0. - person Siewers; 08.02.2012
comment
Я пробовал это, и это не работает. Он не отправляет ожидающие команды после закрытия сеанса без сохранения состояния. - person Victor Marzo; 28.02.2013
comment
На самом деле, это сработало для меня. Я знаю, что этот пост старый, но он может помочь кому-то еще. При 9000+ Вставках с размером пакета 50 транзакция шла, например, от 6310 мс. до 3385 мс. Я еще немного поиграюсь с размером партии, но да, это сработало. - person C3PO; 19.09.2016

Я также обнаружил, что NHibernate не выполняет пакетную вставку в PostgreSQL. Я выделил две возможные причины:

1) Драйвер Npgsql не поддерживает пакетные вставки/обновления (см. форум)

2) NHibernate не имеет *BatchingBatcher(Factory) для PostgreSQL(Npgsql). Я попытался использовать драйвер Devart dotConnect с NHibernate (я написал собственный драйвер для NHibernate), но он все равно не работал.

Я предполагаю, что этот драйвер также должен реализовывать интерфейс IEmbeddedBatcherFactoryProvider, но мне это кажется нетривиальным (использование одного для Oracle не сработало;))

Кому-нибудь удалось заставить Nhibarnate делать пакетные вставки в PostgreSQL или может подтвердить мой вывод?

person stachu    schedule 06.04.2011