sql >> Database >  >> RDS >> Sqlserver

Hoe kan ik in de kortst mogelijke tijd 10 miljoen records invoegen?

Doe alsjeblieft niet maak een DataTable te laden via BulkCopy. Dat is een prima oplossing voor kleinere datasets, maar er is absoluut geen reden om alle 10 miljoen rijen in het geheugen te laden voordat de database wordt aangeroepen.

Uw beste gok (buiten BCP / BULK INSERT / OPENROWSET(BULK...) ) is om de inhoud van het bestand naar de database te streamen via een Table-Valued Parameter (TVP). Door een TVP te gebruiken, kunt u het bestand openen, een rij lezen en een rij verzenden totdat u klaar bent en vervolgens het bestand sluiten. Deze methode heeft een geheugenvoetafdruk van slechts een enkele rij. Ik heb een artikel geschreven, Gegevens streamen naar SQL Server 2008 vanuit een toepassing, waarin een voorbeeld van dit scenario staat.

Een simplistisch overzicht van de structuur is als volgt. Ik ga uit van dezelfde importtabel en veldnaam als getoond in de bovenstaande vraag.

Vereiste database-objecten:

-- 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

C# app-code om gebruik te maken van de bovenstaande SQL-objecten staat hieronder. Merk op dat in plaats van een object (bijv. DataTable) te vullen en vervolgens de Opgeslagen Procedure uit te voeren, het bij deze methode de uitvoering van de Opgeslagen Procedure is die het lezen van de bestandsinhoud initieert. De invoerparameter van de Stored Proc is geen variabele; het is de retourwaarde van een methode, GetFileContents . Die methode wordt aangeroepen wanneer de SqlCommand roept ExecuteNonQuery . aan , die het bestand opent, een rij leest en de rij naar SQL Server stuurt via de IEnumerable<SqlDataRecord> en yield return constructies en sluit vervolgens het bestand. De opgeslagen procedure ziet alleen een tabelvariabele, @ImportTable, die toegankelijk is zodra de gegevens binnenkomen (opmerking:de gegevens blijven een korte tijd bestaan, zelfs als niet de volledige inhoud, 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();
   }
}

De GetFileContents bovenstaande methode wordt gebruikt als de invoerparameterwaarde voor de Opgeslagen Procedure zoals hieronder getoond:

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

Aanvullende opmerkingen:

  1. Met enige aanpassing kan de bovenstaande C#-code worden aangepast om de gegevens in batches te verwerken.
  2. Met een kleine wijziging kan de bovenstaande C#-code worden aangepast om in meerdere velden te worden verzonden (het voorbeeld dat wordt getoond in het artikel "Steaming Data..." waarnaar hierboven is gelinkt, wordt in 2 velden doorgegeven).
  3. U kunt ook de waarde van elk record manipuleren in de SELECT verklaring in het proces.
  4. Je kunt rijen ook uitfilteren door een WHERE-voorwaarde in het proces te gebruiken.
  5. Je hebt meerdere keren toegang tot de TVP-tabelvariabele; het is ALLEEN LEZEN maar niet "alleen doorsturen".
  6. Voordelen boven SqlBulkCopy :
    1. SqlBulkCopy is alleen INSERT, terwijl het gebruik van een TVP ervoor zorgt dat de gegevens op elke manier kunnen worden gebruikt:u kunt MERGE aanroepen; je kunt DELETE gebaseerd op een bepaalde voorwaarde; u kunt de gegevens in meerdere tabellen splitsen; enzovoort.
    2. Omdat een TVP niet alleen INSERT is, heb je geen aparte faseringstabel nodig om de gegevens in te dumpen.
    3. U kunt gegevens uit de database terughalen door ExecuteReader te bellen in plaats van ExecuteNonQuery . Als er bijvoorbeeld een IDENTITY veld op de DATAs importtabel, kunt u een OUTPUT . toevoegen clausule toe aan de INSERT om INSERTED.[ID] terug te geven (ervan uitgaande dat ID is de naam van de IDENTITY veld). Of u kunt de resultaten van een geheel andere zoekopdracht doorgeven, of beide, aangezien meerdere resultatensets kunnen worden verzonden en geopend via Reader.NextResult() . Informatie terughalen uit de database is niet mogelijk bij gebruik van SqlBulkCopy toch zijn er verschillende vragen hier op S.O. van de mensen die precies dat willen doen (tenminste met betrekking tot de nieuw gecreëerde IDENTITY waarden).
    4. Voor meer informatie over waarom het soms sneller is voor het algehele proces, zelfs als het iets langzamer is om de gegevens van schijf naar SQL Server te krijgen, raadpleegt u deze whitepaper van het SQL Server Customer Advisory Team:Maximizing Throughput with TVP


  1. Waarom lijk ik Oracle 11g niet te dwingen meer CPU's te verbruiken voor een enkele SQL-query?

  2. Verbinding maken met de MySQL-database

  3. JSON_SET() vs JSON_INSERT() vs JSON_REPLACE() in MySQL:wat is het verschil?

  4. Converteer integer naar hex en hex naar integer