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

Hvordan kan jeg indsætte 10 millioner poster på kortest mulig tid?

Gør venligst ikke opret en DataTable at indlæse via BulkCopy. Det er en ok løsning til mindre sæt data, men der er absolut ingen grund til at indlæse alle 10 millioner rækker i hukommelsen, før du kalder databasen.

Dit bedste bud (uden for BCP). / BULK INSERT / OPENROWSET(BULK...) ) er at streame indholdet fra filen til databasen via en Table-Valued Parameter (TVP). Ved at bruge en TVP kan du åbne filen, læse en række og sende en række, indtil du er færdig, og derefter lukke filen. Denne metode har et hukommelsesfodaftryk på kun en enkelt række. Jeg skrev en artikel, Streaming Data Into SQL Server 2008 From an Application, som har et eksempel på netop dette scenarie.

En forenklet oversigt over strukturen er som følger. Jeg antager samme importtabel og feltnavn som vist i spørgsmålet ovenfor.

Nødvendige databaseobjekter:

-- 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-kode til at gøre brug af ovenstående SQL-objekter er nedenfor. Læg mærke til, hvordan i stedet for at udfylde et objekt (f.eks. DataTable) og derefter udføre den lagrede procedure, er det i denne metode udførelsen af ​​den lagrede procedure, der starter læsningen af ​​filens indhold. Indgangsparameteren for den Stored Proc er ikke en variabel; det er returværdien af ​​en metode, GetFileContents . Denne metode kaldes, når SqlCommand kalder ExecuteNonQuery , som åbner filen, læser en række og sender rækken til SQL Server via IEnumerable<SqlDataRecord> og yield return konstruerer og lukker derefter filen. Den lagrede procedure ser bare en tabelvariabel, @ImportTable, der kan fås adgang til, så snart dataene begynder at komme over (bemærk:dataene eksisterer i kort tid, selvom ikke det fulde indhold, i 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();
   }
}
 

GetFileContents metoden ovenfor bruges som inputparameterværdien for den lagrede procedure som vist nedenfor:

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

Yderligere bemærkninger:

  1. Med nogle ændringer kan ovenstående C#-kode tilpasses til at batch dataene ind.
  2. Med mindre ændringer kan ovenstående C#-kode tilpasses til at sende i flere felter (eksemplet vist i artiklen "Steaming Data...", der er linket ovenfor, passerer i 2 felter).
  3. Du kan også manipulere værdien af ​​hver post i SELECT erklæring i proc.
  4. Du kan også filtrere rækker fra ved at bruge en WHERE-betingelse i proc.
  5. Du kan få adgang til TVP Table Variable flere gange; det er SKRIVEKUN, men ikke "kun videresend".
  6. Fordele i forhold til SqlBulkCopy :
    1. SqlBulkCopy er kun INSERT, mens brug af en TVP gør det muligt at bruge data på enhver måde:du kan kalde MERGE; du kan DELETE baseret på en eller anden betingelse; du kan opdele dataene i flere tabeller; og så videre.
    2. På grund af en TVP, der ikke kun er INSERT, behøver du ikke en separat iscenesættelsestabel til at dumpe dataene ind i.
    3. Du kan få data tilbage fra databasen ved at kalde ExecuteReader i stedet for ExecuteNonQuery . For eksempel, hvis der var en IDENTITY feltet på DATAs importtabel, kan du tilføje en OUTPUT klausul til INSERT for at sende INSERTED.[ID] tilbage (forudsat ID er navnet på IDENTITY Mark). Eller du kan sende resultaterne af en helt anden forespørgsel tilbage, eller begge dele, da flere resultatsæt kan sendes og tilgås via Reader.NextResult() . Det er ikke muligt at få info tilbage fra databasen, når du bruger SqlBulkCopy alligevel er der flere spørgsmål her på S.O. af folk, der ønsker at gøre præcis det (i det mindste med hensyn til den nyoprettede IDENTITY værdier).
    4. For mere information om, hvorfor det nogle gange er hurtigere for den overordnede proces, selv om det er lidt langsommere med at overføre data fra disken til SQL Server, se venligst dette whitepaper fra SQL Server Customer Advisory Team:Maximizing Throughput with TVP


  1. MONTHS_BETWEEN() Funktion i Oracle

  2. Sådan konverteres små bogstaver til store bogstaver i MySQL

  3. Hvordan gemmer man uuid som nummer?

  4. Find det samlede antal resultater i mySQL-forespørgsel med offset+limit