2011-01-06 14 views
6

Estoy interactuando con una base de datos PostgreSQL con NHibernate.NHibernate no parece hacer Inserción masiva en PostgreSQL

Antecedentes

Hice algunas pruebas sencillas ... parece que está tomando 2 segundos para persistir 300 registros. Tengo un programa Perl con funcionalidad idéntica, pero en lugar de SQL directo, solo toma el 70% del tiempo. No estoy seguro de si esto se espera. Pensé que C#/NHibernate sería más rápido o al menos a la par.

Preguntas

Uno de mi observación es que (con show_sql encendido), el NHibernate está emitiendo inserta un par de cientos de veces, en lugar de hacer de inserción masiva que tomar cuidados de varias filas. Y tenga en cuenta que estoy asignando la clave principal yo mismo, sin usar el generador "nativo".

¿Eso es lo esperado? ¿De todos modos podría hacer que emitiera una declaración GRATUITA de INSERT? Me parece que esta podría ser una de las áreas en las que podría acelerar el rendimiento.

+0

Si puede convencer a nhibernate de que use 'copy from' en lugar de 'insert', lo más probable es que ejecute un orden de magnitud más rápido. Eso puede ser lo que está haciendo el programa perl. –

Respuesta

2

También encontré que NHibernate no está haciendo inserciones por lotes en PostgreSQL. I identificaron dos posibles razones:

1) conductor Npgsql no soporta inserciones de proceso por lotes/actualizaciones (see forum)

2) NHibernate no tiene * BatchingBatcher (Factory) para PostgreSQL (Npgsql). Intenté usar el controlador Devart dotConnect con NHibernate (escribí un controlador personalizado para NHibernate) pero aún así no funcionó.

supongo que este controlador también debe implementar la interfaz IEmbeddedBatcherFactoryProvider, pero no parece trivial para mí (usando una para Oracle no funcionó;))

¿Alguien logrado forzar Nhibarnate hacer inserciones de lote a PostgreSQL o lata confirmar mi conclusión?

6

Como stachu encuentran correctamente: NHibernate no tiene * BatchingBatcher (de fábrica) para PostgreSQL (Npgsql) Como stachu Askes: ¿Alguien logró forzar Nhibarnate hacer inserciones lotes para PostgreSQL

me escribió un dosificador que no utiliza ninguna materia dosificación Npgsql, pero no manipular el "estilo de la vieja escuela" cadena SQL (INSERT INTO [..] Valores (...), (...), ...)

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; } 
     } 
    } 
} 
+1

Debo mencionar que necesitará establecer la propiedad 'adonet.factory_class', en la configuración NHibernate, al nombre completo de la clase' PostgresClientBatchingBatcherFactory' y, por supuesto, establecer 'adonet.batch_size' en un número mayor que '0'. – Siewers

+0

He intentado esto y no funciona. No envía los comandos pendientes después de cerrar una sesión sin estado. –

+0

En realidad, funcionó para mí. Sé que esta publicación es antigua, pero podría ayudar a otra persona. Con más de 9000 inserciones con un tamaño de lote de 50, la transacción pasó de, por ejemplo, 6310 ms. a 3385 ms. Jugaré con el tamaño del lote un poco más, pero sí, funcionó. –

Cuestiones relacionadas