Sqlserver
 sql >> Database >  >> RDS >> Sqlserver

Come posso inserire 10 milioni di record nel minor tempo possibile?

Per favore non crea una DataTable da caricare tramite BulkCopy. Questa è una buona soluzione per set di dati più piccoli, ma non c'è assolutamente alcun motivo per caricare tutti i 10 milioni di righe in memoria prima di chiamare il database.

La soluzione migliore (al di fuori di BCP / BULK INSERT / OPENROWSET(BULK...) ) è quello di trasmettere il contenuto del file al database tramite un parametro con valori di tabella (TVP). Usando un TVP puoi aprire il file, leggere una riga e inviare una riga fino al termine, quindi chiudere il file. Questo metodo ha un footprint di memoria di una sola riga. Ho scritto un articolo, Streaming Data Into SQL Server 2008 From an Application, che contiene un esempio di questo scenario.

Una panoramica semplicistica della struttura è la seguente. Presumo la stessa tabella di importazione e il nome del campo come mostrato nella domanda precedente.

Oggetti database richiesti:

-- First: You need a User-Defined Table Type
CREATE TYPE ImportStructure AS TABLE (Field VARCHAR(MAX));
GO

-- Second: Use the UDTT as an input param to an import proc.
--         Hence "Tabled-Valued Parameter" (TVP)
CREATE PROCEDURE dbo.ImportData (
   @ImportTable    dbo.ImportStructure READONLY
)
AS
SET NOCOUNT ON;

-- maybe clear out the table first?
TRUNCATE TABLE dbo.DATAs;

INSERT INTO dbo.DATAs (DatasField)
    SELECT  Field
    FROM    @ImportTable;

GO

Di seguito è riportato il codice dell'app C# per utilizzare gli oggetti SQL precedenti. Si noti come invece di riempire un oggetto (es. DataTable) e quindi eseguire la Stored Procedure, in questo metodo è l'esecuzione della Stored Procedure ad avviare la lettura del contenuto del file. Il parametro di input di Stored Proc non è una variabile; è il valore di ritorno di un metodo, GetFileContents . Questo metodo viene chiamato quando SqlCommand chiama ExecuteNonQuery , che apre il file, legge una riga e invia la riga a SQL Server tramite il IEnumerable<SqlDataRecord> e yield return costruisce, quindi chiude il file. La stored procedure vede solo una variabile di tabella, @ImportTable, a cui è possibile accedere non appena i dati iniziano a venire fuori (nota:i dati persistono per un breve periodo, anche se non l'intero contenuto, in tempdb ).

using System.Collections;
using System.Data;
using System.Data.SqlClient;
using System.IO;
using Microsoft.SqlServer.Server;

private static IEnumerable<SqlDataRecord> GetFileContents()
{
   SqlMetaData[] _TvpSchema = new SqlMetaData[] {
      new SqlMetaData("Field", SqlDbType.VarChar, SqlMetaData.Max)
   };
   SqlDataRecord _DataRecord = new SqlDataRecord(_TvpSchema);
   StreamReader _FileReader = null;

   try
   {
      _FileReader = new StreamReader("{filePath}");

      // read a row, send a row
      while (!_FileReader.EndOfStream)
      {
         // You shouldn't need to call "_DataRecord = new SqlDataRecord" as
         // SQL Server already received the row when "yield return" was called.
         // Unlike BCP and BULK INSERT, you have the option here to create a string
         // call ReadLine() into the string, do manipulation(s) / validation(s) on
         // the string, then pass that string into SetString() or discard if invalid.
         _DataRecord.SetString(0, _FileReader.ReadLine());
         yield return _DataRecord;
      }
   }
   finally
   {
      _FileReader.Close();
   }
}

Il GetFileContents il metodo precedente viene utilizzato come valore del parametro di input per la stored procedure come mostrato di seguito:

public static void test()
{
   SqlConnection _Connection = new SqlConnection("{connection string}");
   SqlCommand _Command = new SqlCommand("ImportData", _Connection);
   _Command.CommandType = CommandType.StoredProcedure;

   SqlParameter _TVParam = new SqlParameter();
   _TVParam.ParameterName = "@ImportTable";
   _TVParam.TypeName = "dbo.ImportStructure";
   _TVParam.SqlDbType = SqlDbType.Structured;
   _TVParam.Value = GetFileContents(); // return value of the method is streamed data
   _Command.Parameters.Add(_TVParam);

   try
   {
      _Connection.Open();

      _Command.ExecuteNonQuery();
   }
   finally
   {
      _Connection.Close();
   }

   return;
}

Note aggiuntive:

  1. Con alcune modifiche, il codice C# sopra può essere adattato per inserire i dati in batch.
  2. Con una piccola modifica, il codice C# sopra può essere adattato per l'invio in più campi (l'esempio mostrato nell'articolo "Dati Steaming..." collegato sopra passa in 2 campi).
  3. Puoi anche manipolare il valore di ogni record in SELECT dichiarazione nel proc.
  4. Puoi anche filtrare le righe utilizzando una condizione WHERE nella procedura
  5. Puoi accedere più volte alla variabile Tabella TVP; è READONLY ma non "forward only".
  6. Vantaggi rispetto a SqlBulkCopy :
    1. SqlBulkCopy è solo INSERT mentre l'utilizzo di un TVP consente di utilizzare i dati in qualsiasi modo:puoi chiamare MERGE; puoi DELETE in base a qualche condizione; puoi dividere i dati in più tabelle; e così via.
    2. Dato che un TVP non è solo INSERT, non è necessaria una tabella di staging separata in cui scaricare i dati.
    3. Puoi recuperare i dati dal database chiamando ExecuteReader invece di ExecuteNonQuery . Ad esempio, se esisteva un IDENTITY campo su DATAs import table, puoi aggiungere un OUTPUT clausola al INSERT per restituire INSERTED.[ID] (supponendo ID è il nome dell'IDENTITY campo). Oppure puoi restituire i risultati di una query completamente diversa, o entrambi poiché è possibile inviare e accedere a più set di risultati tramite Reader.NextResult() . Non è possibile recuperare informazioni dal database quando si utilizza SqlBulkCopy eppure ci sono diverse domande qui su S.O. di persone che vogliono fare esattamente questo (almeno per quanto riguarda la IDENTITY appena creata valori).
    4. Per ulteriori informazioni sul motivo per cui a volte è più veloce per l'intero processo, anche se leggermente più lento nell'ottenere i dati dal disco in SQL Server, consultare questo whitepaper del team di consulenza per i clienti di SQL Server:Maximizing Throughput with TVP