Domanda

Ho avuto qualcosa del genere nel mio codice (.Net 2.0, MS SQL)

SqlConnection connection = new SqlConnection(@"Data Source=localhost;Initial
Catalog=DataBase;Integrated Security=True");
  connection.Open();

  SqlCommand cmdInsert = connection.CreateCommand();
  SqlTransaction sqlTran = connection.BeginTransaction();
  cmdInsert.Transaction = sqlTran;

  cmdInsert.CommandText =
     @"INSERT INTO MyDestinationTable" +
      "(Year, Month, Day, Hour,  ...) " +
      "VALUES " +
      "(@Year, @Month, @Day, @Hour, ...) ";

  cmdInsert.Parameters.Add("@Year", SqlDbType.SmallInt);
  cmdInsert.Parameters.Add("@Month", SqlDbType.TinyInt);
  cmdInsert.Parameters.Add("@Day", SqlDbType.TinyInt);
  // more fields here
  cmdInsert.Prepare();

  Stream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read);

  StreamReader reader = new StreamReader(stream);
  char[] delimeter = new char[] {' '};
  String[] records;
  while (!reader.EndOfStream)
  {
    records = reader.ReadLine().Split(delimeter, StringSplitOptions.None);

    cmdInsert.Parameters["@Year"].Value = Int32.Parse(records[0].Substring(0, 4));
    cmdInsert.Parameters["@Month"].Value = Int32.Parse(records[0].Substring(5, 2));
    cmdInsert.Parameters["@Day"].Value = Int32.Parse(records[0].Substring(8, 2));
    // more here complicated stuff here
    cmdInsert.ExecuteNonQuery()
  }
  sqlTran.Commit();
  connection.Close();

Con cmdInsert.ExecuteNonQuery () commentato questo codice viene eseguito in meno di 2 secondi. Con l'esecuzione SQL ci vogliono 1m 20 sec. Ci sono circa 0,5 milioni di record. La tabella viene svuotata prima. L'attività del flusso di dati SSIS con funzionalità simili richiede circa 20 secondi.

  • Inserimento di massa non era non un'opzione (vedi sotto). Ho fatto delle cose fantasiose durante questa importazione.
  • La mia macchina di prova è Core 2 Duo con 2 GB di RAM.
  • Quando si guarda in Task Manager la CPU non è stata completamente ottimizzata. Sembra che anche l'IO non sia stata pienamente utilizzata.
  • Lo schema è semplice come l'inferno: una tabella con AutoInt come indice primario e meno di 10 pollici, minuscoli e caratteri (10).

Dopo alcune risposte qui ho scoperto che è possibile eseguire copia in blocco dalla memoria ! Mi stavo rifiutando di usare la copia di massa perché pensavo che dovesse essere fatto dal file ...

Ora lo uso e ci vogliono circa 20 secondi (come l'attività SSIS)

  DataTable dataTable = new DataTable();

  dataTable.Columns.Add(new DataColumn("ixMyIndex", System.Type.GetType("System.Int32")));   
  dataTable.Columns.Add(new DataColumn("Year", System.Type.GetType("System.Int32")));   
  dataTable.Columns.Add(new DataColumn("Month", System.Type.GetType("System.Int32")));
  dataTable.Columns.Add(new DataColumn("Day", System.Type.GetType("System.Int32")));
 // ... and more to go

  DataRow dataRow;
  object[] objectRow = new object[dataTable.Columns.Count];

  Stream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read);

  StreamReader reader = new StreamReader(stream);
  char[] delimeter = new char[] { ' ' };
  String[] records;
  int recordCount = 0;
  while (!reader.EndOfStream)
  {
    records = reader.ReadLine().Split(delimeter, StringSplitOptions.None);

    dataRow = dataTable.NewRow();
    objectRow[0] = null; 
    objectRow[1] = Int32.Parse(records[0].Substring(0, 4));
    objectRow[2] = Int32.Parse(records[0].Substring(5, 2));
    objectRow[3] = Int32.Parse(records[0].Substring(8, 2));
    // my fancy stuf goes here

    dataRow.ItemArray = objectRow;         
    dataTable.Rows.Add(dataRow);

    recordCount++;
  }

  SqlBulkCopy bulkTask = new SqlBulkCopy(connection, SqlBulkCopyOptions.TableLock, null);
  bulkTask.DestinationTableName = "MyDestinationTable"; 
  bulkTask.BatchSize = dataTable.Rows.Count;
  bulkTask.WriteToServer(dataTable);
  bulkTask.Close();
È stato utile?

Soluzione

Invece di inserire ogni singolo record, prova a usare Classe SqlBulkCopy per inserire in blocco tutti i record in una sola volta.

Crea una DataTable e aggiungi tutti i tuoi record alla DataTable, quindi usa SqlBulkCopy . WriteToServer da inserire in blocco tutti i dati contemporaneamente.

Altri suggerimenti

È richiesta la transazione? L'uso della transazione richiede molte più risorse rispetto ai semplici comandi.

Inoltre, se si è certi che i valori inseriti siano corretti, è possibile utilizzare un BulkInsert.

1 minuto sembra abbastanza ragionevole per 0,5 milioni di dischi. È un record ogni 0,00012 secondi.

La tabella ha degli indici? Rimuoverli e riapplicarli dopo l'inserimento di massa migliorerebbe le prestazioni degli inserti, se questa è un'opzione.

Non mi sembra irragionevole elaborare 8.333 record al secondo ... che tipo di throughput ti aspetti?

Se hai bisogno di una maggiore velocità, potresti prendere in considerazione l'implementazione dell'inserimento di massa:

http://msdn.microsoft.com/en-us/library /ms188365.aspx

Se una qualche forma di inserimento in blocco non è un'opzione, l'altro sarebbe più thread, ognuno con la propria connessione al database.

Il problema con il sistema attuale è che hai 500.000 round trip nel database e stai aspettando che il primo round trip si completi prima di iniziare il successivo - qualsiasi tipo di latenza (cioè una rete tra le macchine) significherà che la maggior parte del tempo è trascorso in attesa.

Se riesci a dividere il lavoro, magari usando una qualche forma di impostazione produttore / consumatore, potresti scoprire che puoi ottenere molto più utilizzo di tutte le risorse.

Tuttavia, per fare ciò dovrai perdere l'unica grande transazione, altrimenti il ??primo thread del writer bloccherà tutte le altre fino al completamento della sua transazione. Puoi ancora utilizzare le transazioni, ma dovrai usarne molte di piccole dimensioni anziché una grande.

L'SSIS sarà veloce perché utilizza il metodo di inserimento bulk: esegui prima tutta l'elaborazione complicata, genera l'elenco finale di dati da inserire e dai tutto allo stesso tempo da inserire in blocco.

Suppongo che ciò che richiede circa 58 secondi sia l'inserimento fisico di 500.000 record, quindi si ottengono circa 10.000 inserti al secondo. Senza conoscere le specifiche del tuo server di database (vedo che stai usando localhost, quindi i ritardi di rete non dovrebbero essere un problema), è difficile dire se questo è buono, cattivo o terribile.

Guarderei il tuo schema di database - ci sono un sacco di indici sulla tabella che devono essere aggiornati dopo ogni inserimento? Potrebbe trattarsi di altre tabelle con chiavi esterne che fanno riferimento alla tabella su cui si sta lavorando. Esistono strumenti di profilazione SQL e funzionalità di monitoraggio delle prestazioni integrati in SQL Server, ma non li ho mai usati. Ma possono mostrare problemi come i lucchetti e cose del genere.

Fai le cose fantasiose sui dati, prima su tutti i record. Quindi inserirli in blocco.

(poiché non si esegue la selezione dopo un inserimento .. non vedo il problema di applicare tutte le operazioni sui dati prima di BulkInsert

Se dovessi indovinare, la prima cosa che vorrei cercare sono troppi o il tipo sbagliato di indici nella tabella tbTrafficLogTTL. Senza guardare la definizione dello schema per la tabella, non posso davvero dirlo, ma ho riscontrato problemi di prestazioni simili quando:

  1. La chiave primaria è un GUID e l'indice primario è CLUSTERED.
  2. Esiste una sorta di indice UNICO su una serie di campi.
  3. Ci sono troppi indici sulla tabella.

Quando inizi a indicizzare mezzo milione di righe di dati, aumenta il tempo impiegato per creare e gestire gli indici.

Noterò anche che se hai qualche opzione per convertire i campi Anno, Mese, Giorno, Ora, Minuti, Secondi in un singolo campo datetime2 o timestamp, dovresti. Stai aggiungendo molta complessità alla tua architettura di dati, senza alcun guadagno. L'unico motivo per cui potrei anche contemplare l'utilizzo di una struttura a campi divisi in questo modo è se hai a che fare con uno schema di database preesistente che non può essere modificato per nessun motivo. In tal caso, fa schifo essere tu.

Ho avuto un problema simile nel mio ultimo contratto. Stai facendo 500.000 viaggi in SQL per inserire i tuoi dati. Per un notevole aumento delle prestazioni, si desidera esaminare il metodo BulkInsert nello spazio dei nomi SQL. Ho dovuto "ricaricare" processi che sono passati da più di 2 ore per ripristinare un paio di dozzine di tabelle fino a 31 secondi dopo aver implementato l'importazione in blocco.

Questo potrebbe essere realizzato meglio usando qualcosa come il comando bcp. Se ciò non è disponibile, i suggerimenti sopra riportati sull'utilizzo di BULK INSERT sono la soluzione migliore. Stai facendo 500.000 round trip nel database e scrivi 500.000 voci nei file di registro, per non parlare dello spazio che deve essere assegnato al file di registro, alla tabella e agli indici.

Se stai inserendo un ordine diverso dall'indice cluster, devi anche occuparti del tempo necessario per riorganizzare i dati fisici sul disco. Ci sono molte variabili qui che potrebbero far rallentare la tua query di quanto vorresti.

~ 10.000 transazioni al secondo non sono terribili per i singoli inserti che vengono arrotondati dal codice /

BULK INSERT = bcp da un'autorizzazione

È possibile raggruppare gli INSERTI per ridurre i viaggi di andata e ritorno SQLDataAdaptor.UpdateBatchSize = 10000 offre 50 round trip

Hai ancora 500k inserti però ...

Articolo

MSDN

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top