2011-01-06 21 views
6

Współpracuję z bazą danych PostgreSQL z NHibernate.NHibernate nie wydaje się masowego wstawiania do PostgreSQL

Tło

Zrobiłem kilka prostych testów ... wydaje się, że zabiera 2 sekundy, aby utrzymywać 300 rekordów. Mam program Perl z identyczną funkcjonalnością, ale zamiast tego wydaje się bezpośredni SQL, zajmuje tylko 70% czasu. Nie jestem pewien, czy jest to oczekiwane. Myślałem, że C#/NHibernate będzie szybszy lub przynajmniej na równi.

Pytania

Jeden z moich obserwacji wynika, że ​​(z show_sql włączone), The NHibernate jest wydawanie wstawia kilkaset razy, zamiast robić luzem INSERT że podjęcie troski wielu rzędach. I proszę, sam przypisuję klucz podstawowy, nie używając "natywnego" generatora.

Czy to jest oczekiwane? Czy mimo to mógłbym zamiast tego wydać polecenie INSERT bulk? Wydaje mi się, że może to być jeden z obszarów, w którym mogę przyspieszyć występ.

+0

Jeśli możesz przekonać nhibernate do użycia 'copy from' zamiast' insert', które najprawdopodobniej będzie działało o rząd wielkości szybciej. Być może właśnie to robi program perl. –

Odpowiedz

2

Odkryłem także, że NHibernate nie robi wsadowych wstawek do PostgreSQL. zidentyfikowałem dwa możliwe powody:

1) kierowca Npgsql nie obsługuje wkładki wsadowe/aktualizacje (see forum)

2) NHibernate nie posiada * BatchingBatcher (fabryczne) dla PostgreSQL (Npgsql). Próbowałem używać sterownika DevCon dotConnect z NHibernate (napisałem niestandardowy sterownik dla NHibernate), ale i tak nie zadziałało.

Przypuszczam, to kierowca powinien również wdrożenie interfejsu IEmbeddedBatcherFactoryProvider, ale nie wydaje się trywialny dla mnie (stosując jeden dla Oracle nie pracował;))

Czy ktokolwiek zdołał zmusić Nhibarnate zrobić wkładki wsadowych do PostgreSQL lub puszka potwierdzić mój wniosek?

6

Jak Stachu dowiedział się prawidłowo: NHibernate nie posiada * BatchingBatcher (fabryczne) dla PostgreSQL (Npgsql) Jak Stachu askes for: Czy ktoś zdołał zmusić Nhibarnate zrobić wkładki wsadowych do PostgreSQL

Napisałem Dozowniki że nie używać żadnych Npgsql dozowania rzeczy, ale nie manipulować SQL string "oldschool styl" (INSERT INTO [..] Wartości (...) (...), ...)

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

Powinienem wspomnieć, że będziesz musiał ustawić właściwość 'adonet.factory_class' w konfiguracji NHibernate na pełną nazwę klasy' PostgresClientBatchingBatcherFactory' i oczywiście ustawić "adonet.batch_size" na liczbę większą niż '0'. – Siewers

+0

Próbowałem tego i to nie działa. Nie wysyła oczekujących poleceń po zakończeniu sesji statystycznej. –

+0

Właściwie to zadziałało dla mnie. Wiem, że ten post jest stary, ale może pomóc komuś innemu. W przypadku 9000+ wkładek o rozmiarze wsadu 50 transakcja została przerwana, na przykład 6310 ms. do 3385 ms. Będę grał z wielkością partii nieco więcej, ale tak, zadziałało. –