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:
- Z pewnymi modyfikacjami, powyższy kod C# można dostosować do wsadu danych.
- 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).
- Możesz także manipulować wartością każdego rekordu w
SELECToświadczenie w proc. - Możesz także odfiltrować wiersze, używając warunku WHERE w procedurze.
- Możesz wielokrotnie uzyskiwać dostęp do zmiennej tabeli TVP; jest TYLKO DO ODCZYTU, ale nie „tylko do przodu”.
- Przewagi nad
SqlBulkCopy:SqlBulkCopyjest tylko INSERT, podczas gdy korzystanie z TVP pozwala na wykorzystanie danych w dowolny sposób:możesz wywołaćMERGE; możeszDELETEna podstawie pewnego warunku; możesz podzielić dane na wiele tabel; i tak dalej.- Ponieważ TVP nie jest tylko INSERT, nie potrzebujesz oddzielnej tabeli pomostowej, aby zrzucić dane.
- Możesz odzyskać dane z bazy danych, wywołując
ExecuteReaderzamiastExecuteNonQuery. Na przykład, jeśli istniejeIDENTITYpole naDATAsimportuj tabelę, możesz dodaćOUTPUTklauzula doINSERTaby przekazaćINSERTED.[ID](zakładającIDto nazwaIDENTITYpole). 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ą funkcjiReader.NextResult(). Pobieranie informacji z bazy danych nie jest możliwe przy użyciuSqlBulkCopyjednak jest kilka pytań tutaj na S.O. ludzi, którzy chcą to zrobić (przynajmniej w odniesieniu do nowo utworzonejIDENTITYwartości). - 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