Sqlserver
 sql >> Baza danych >  >> RDS >> Sqlserver

Jak mogę wstawić 10 milionów rekordów w najkrótszym możliwym czasie?

Proszę nie utwórz DataTable do załadowania za pomocą BulkCopy. Jest to dobre rozwiązanie dla mniejszych zestawów danych, ale nie ma absolutnie żadnego powodu, aby ładować wszystkie 10 milionów wierszy do pamięci przed wywołaniem bazy danych.

Twój najlepszy zakład (poza BCP / BULK INSERT / OPENROWSET(BULK...) ) ma na celu strumieniowe przesyłanie zawartości pliku do bazy danych za pośrednictwem parametru z wartościami tabelarycznymi (TVP). Korzystając z TVP możesz otworzyć plik, odczytać wiersz i wysłać wiersz, aż skończysz, a następnie zamknąć plik. Ta metoda ma ślad w pamięci wynoszący tylko jeden wiersz. Napisałem artykuł, Strumieniowanie danych do SQL Server 2008 z aplikacji, który zawiera przykład tego właśnie scenariusza.

Uproszczony przegląd struktury jest następujący. Zakładam tę samą nazwę tabeli i pola importu, jak pokazano w powyższym pytaniu.

Wymagane obiekty bazy danych:

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

Kod aplikacji C# do korzystania z powyższych obiektów SQL znajduje się poniżej. Zauważ, że zamiast wypełniania obiektu (np. DataTable), a następnie wykonywania procedury składowanej, w tej metodzie to wykonanie procedury składowanej inicjuje odczyt zawartości pliku. Parametr wejściowy Stored Proc nie jest zmienną; jest to wartość zwracana przez metodę, GetFileContents . Ta metoda jest wywoływana, gdy SqlCommand wywołuje ExecuteNonQuery , który otwiera plik, odczytuje wiersz i wysyła go do SQL Server za pośrednictwem IEnumerable<SqlDataRecord> i yield return konstrukcje, a następnie zamyka plik. Procedura składowana po prostu widzi zmienną tabeli @ImportTable, do której można uzyskać dostęp, gdy tylko dane zaczną się pojawiać (uwaga:dane utrzymują się przez krótki czas, nawet jeśli nie pełna zawartość, w 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 Powyższa metoda jest używana jako wartość parametru wejściowego dla procedury składowanej, jak pokazano poniżej:

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

Dodatkowe uwagi:

  1. Z pewnymi modyfikacjami, powyższy kod C# można dostosować do wsadu danych.
  2. Z drobną modyfikacją powyższy kod C# można dostosować do wysyłania w wielu polach (przykład pokazany w artykule „Steaming Data...” połączonym powyżej przechodzi w 2 pola).
  3. Możesz także manipulować wartością każdego rekordu w SELECT oświadczenie w proc.
  4. Możesz także odfiltrować wiersze, używając warunku WHERE w procedurze.
  5. Możesz wielokrotnie uzyskiwać dostęp do zmiennej tabeli TVP; jest TYLKO DO ODCZYTU, ale nie „tylko do przodu”.
  6. Przewagi nad SqlBulkCopy :
    1. SqlBulkCopy jest tylko INSERT, podczas gdy korzystanie z TVP pozwala na wykorzystanie danych w dowolny sposób:możesz wywołać MERGE; możesz DELETE na podstawie pewnego warunku; możesz podzielić dane na wiele tabel; i tak dalej.
    2. Ponieważ TVP nie jest tylko INSERT, nie potrzebujesz oddzielnej tabeli pomostowej, aby zrzucić dane.
    3. Możesz odzyskać dane z bazy danych, wywołując ExecuteReader zamiast ExecuteNonQuery . Na przykład, jeśli istnieje IDENTITY pole na DATAs importuj tabelę, możesz dodać OUTPUT klauzula do INSERT aby przekazać INSERTED.[ID] (zakładając ID to nazwa IDENTITY pole). Możesz też przekazać wyniki zupełnie innego zapytania, albo jedno i drugie, ponieważ wiele zestawów wyników można wysłać i uzyskać do nich dostęp za pomocą funkcji Reader.NextResult() . Pobieranie informacji z bazy danych nie jest możliwe przy użyciu SqlBulkCopy jednak jest kilka pytań tutaj na S.O. ludzi, którzy chcą to zrobić (przynajmniej w odniesieniu do nowo utworzonej IDENTITY wartości).
    4. Aby uzyskać więcej informacji na temat tego, dlaczego czasami cały proces jest szybszy, nawet jeśli jest nieco wolniejszy przy pobieraniu danych z dysku do SQL Server, zapoznaj się z tym dokumentem zespołu doradztwa dla klientów SQL Server:Maksymalizacja przepustowości dzięki TVP


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. SELECT max(x) zwraca wartość null; jak mogę sprawić, żeby zwróciło 0?

  2. Cloud Migration 101:przejście z programu SQL Server na platformę Azure

  3. Jak wykonać wyzwalacz tylko po zaktualizowaniu określonej kolumny (SQL Server)

  4. Jak zmienić kolumnę z wartości Null na Not Null w SQL Server

  5. Wygeneruj klasę z tabeli bazy danych