NHibernate 似乎没有批量插入 PostgreSQL

Posted

技术标签:

【中文标题】NHibernate 似乎没有批量插入 PostgreSQL【英文标题】:NHibernate does not seems doing Bulk Inserting into PostgreSQL 【发布时间】:2011-06-04 10:10:27 【问题描述】:

我正在使用 NHibernate 与 PostgreSQL 数据库交互。

背景

我做了一些简单的测试...似乎需要 2 秒才能保存 300 条记录。 我有一个功能相同的 Perl 程序,但是直接发出 SQL,只需要 70% 的时间。 我不确定这是否是预期的。我认为 C#/NHibernate 会更快,或者至少是相当的。

问题

我的观察之一是(打开show_sql),NHibernate 发出数百次 INSERT,而不是执行处理多行的批量 INSERT。请注意,我是自己分配主键,而不是使用“本机”生成器。

这是预期的吗?无论如何我可以让它发出批量 INSERT 语句吗?在我看来,这可能是我可以加快性能的领域之一。

【问题讨论】:

如果你能说服 nhibernate 使用 copy from 而不是 insert 它很可能运行速度快一个数量级。这可能就是 perl 程序正在做的事情。 【参考方案1】:

正如 stachu 正确发现的那样:NHibernate 没有 *BatchingBatcher(Factory) for PostgreSQL(Npgsql) 正如 stachu 所问:是否有人设法强制 Nhibarnate 对 PostgreSQL 进行批量插入

我写了一个 Batcher,它不使用任何 Npgsql 批处理东西,但确实操作 SQL 字符串“oldschool 风格”(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; 
        
    

【讨论】:

我应该提一下,您需要在 NHibernate 配置中将 adonet.factory_class 属性设置为 PostgresClientBatchingBatcherFactory 类的完全限定名称,当然还要将 adonet.batch_size 设置为大于0 的数字。 我已经尝试过了,但它不起作用。关闭无状态会话后,它不会发送待处理的命令。 实际上,它对我有用。我知道这篇文章很旧,但它可能对其他人有所帮助。对于批量大小为 50 的 9000 多个插入,事务从例如 6310 毫秒开始。到 3385 毫秒。我会更多地使用批量大小,但是是的,它起作用了。【参考方案2】:

我还发现 NHibernate 没有批量插入 PostgreSQL。 我确定了两个可能的原因:

1) Npgsql 驱动不支持批量插入/更新 (see forum)

2) NHibernate 没有用于 PostgreSQL(Npgsql) 的 *BatchingBatcher(Factory)。我尝试将 Devart dotConnect 驱动程序与 NHibernate 一起使用(我为 NHibernate 编写了自定义驱动程序),但它仍然没有用。

我想这个驱动程序也应该实现 IEmbeddedBatcherFactoryProvider 接口,但这对我来说似乎不是微不足道的(对 Oracle 使用一个没有用;))

是否有人设法强制 Nhibarnate 对 PostgreSQL 进行批量插入,或者可以证实我的结论?

【讨论】:

以上是关于NHibernate 似乎没有批量插入 PostgreSQL的主要内容,如果未能解决你的问题,请参考以下文章

插入时 NHibernate 组件非空属性

NHibernate中的批量更新

使用 NHibernate CreateQuery 插入 Json TEXT 数据

nhibernate save() 生成插入语句,但没有将实际记录插入到数据库中

来自 EFCore.BulkExtensions 的 BulkInsertAsync 似乎没有插入任何内容

在 Nhibernate 中选择后出现意外的批量更新命令 [重复]