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 CreateQuery 插入 Json TEXT 数据
nhibernate save() 生成插入语句,但没有将实际记录插入到数据库中