Как я могу вставить 10 миллионов записей в кратчайшие сроки?

У меня есть файл (который содержит 10 миллионов записей), как показано ниже:

    line1
    line2
    line3
    line4
   .......
    ......
    10 million lines

Итак, в основном я хочу вставить 10 миллионов записей в базу данных. поэтому я прочитал файл и загрузил его на SQL Server.

Код С#

System.IO.StreamReader file = 
    new System.IO.StreamReader(@"c:\test.txt");
while((line = file.ReadLine()) != null)
{
    // insertion code goes here
    //DAL.ExecuteSql("insert into table1 values("+line+")");
}

file.Close();

но вставка займет много времени. Как я могу вставить 10 миллионов записей в кратчайшие сроки с помощью С#?

Обновление 1:
Bulk INSERT:

BULK INSERT DBNAME.dbo.DATAs
FROM 'F:\dt10000000\dt10000000.txt'
WITH
(

     ROWTERMINATOR =' \n'
  );

Моя таблица выглядит следующим образом:

DATAs
(
     DatasField VARCHAR(MAX)
)

но я получаю следующую ошибку:

Msg 4866, уровень 16, состояние 1, строка 1
Сбой массовой загрузки невозможен. Столбец слишком длинный в файле данных для строки 1, столбец 1. Убедитесь, что терминатор поля и ограничитель строки указаны правильно.

Msg 7399, уровень 16, состояние 1, строка 1
Поставщик OLE DB "BULK" для связанного сервера "(null)" сообщил об ошибке. Поставщик не предоставил никакой информации об ошибке.

Msg 7330, уровень 16, состояние 2, строка 1
Не удается получить строку из поставщика OLE DB "BULK" для связанного сервера "(null)".

Ниже приведен код:

BULK INSERT DBNAME.dbo.DATAs
FROM 'F:\dt10000000\dt10000000.txt'
WITH
(
    FIELDTERMINATOR = '\t',
    ROWTERMINATOR = '\n'
);

Ответ 1

Пожалуйста, не создавайте DataTable для загрузки через BulkCopy. Это хорошее решение для небольших наборов данных, но нет абсолютно никакой причины загружать все 10 миллионов строк в память перед вызовом базы данных.

Ваш лучший выбор (за пределами BCP/BULK INSERT/OPENROWSET(BULK...)) - передать содержимое из файла в базу данных через Table-Valued Parameter (TVP). Используя TVP, вы можете открыть файл, прочитать строку и отправить строку до завершения, а затем закрыть файл. Этот метод имеет объем памяти всего одной строки. Я написал статью Поток данных в SQL Server 2008 из приложения, в котором есть пример этого сценария.

Простейший обзор структуры выглядит следующим образом. Я принимаю ту же таблицу импорта и имя поля, что и в предыдущем вопросе.

Необходимые объекты базы данных:

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

Код приложения С# для использования вышеуказанных объектов SQL приведен ниже. Обратите внимание, что вместо заполнения объекта (например, DataTable), а затем выполнения хранимой процедуры в этом методе выполняется выполнение хранимой процедуры, которая инициирует чтение содержимого файла. Входной параметр Stored Proc не является переменной; это возвращаемое значение метода, GetFileContents. Этот метод вызывается, когда SqlCommand вызывает ExecuteNonQuery, который открывает файл, читает строку и отправляет строку SQL Server с помощью конструкций IEnumerable<SqlDataRecord> и yield return, а затем закрывает файл. Хранимая процедура просто видит переменную таблицы @ImportTable, доступ к которой возможен, как только данные начнут поступать (обратите внимание: данные сохраняются в течение короткого времени, даже если не полное содержимое, в 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 используется как значение входного параметра для хранимой процедуры, как показано ниже:

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

Дополнительные примечания:

  • С некоторой модификацией приведенный выше код С# может быть адаптирован для пакетной обработки данных.
  • При незначительной модификации вышеуказанный код С# может быть адаптирован для отправки в нескольких полях (пример, указанный в статье "Пропаривание данных...", приведенной выше, проходит в 2 полях).
  • Вы также можете манипулировать значением каждой записи в инструкции SELECT в proc.
  • Вы также можете отфильтровать строки, используя условие WHERE в proc.
  • Вы можете получить доступ к переменной TVP Table Variable несколько раз; это READONLY, но не "только вперед".
  • Преимущества над SqlBulkCopy:
    • SqlBulkCopy является INSERT-only, тогда как использование TVP позволяет использовать данные любым способом: вы можете вызвать MERGE; вы можете DELETE на основании некоторого условия; вы можете разделить данные на несколько таблиц; и т.д.
    • Из-за того, что TVP не является INSERT-only, вам не нужна отдельная промежуточная таблица для сброса данных.
    • Вы можете вернуть данные из базы данных, вызвав ExecuteReader вместо ExecuteNonQuery. Например, если в таблице импорта DATAs было поле IDENTITY, вы могли бы добавить предложение OUTPUT в INSERT для возврата назад INSERTED.[ID] (предполагается, что ID - это имя IDENTITY > ). Или вы можете передать результаты совершенно другого запроса или оба, поскольку множество наборов результатов можно отправлять и получать через Reader.NextResult(). Получение информации из базы данных невозможно при использовании SqlBulkCopy, но здесь есть несколько вопросов по S.O. людей, которые хотят сделать именно это (по крайней мере, в отношении вновь созданных значений IDENTITY).
    • Для получения дополнительной информации о том, почему это происходит быстрее для общего процесса, даже если он немного медленнее при получении данных с диска в SQL Server, см. этот технический документ от Консультативной группы клиентов SQL Server: Максимизация пропускной способности с помощью TVP

Ответ 3

лучший способ - это сочетание между вашим 1-м решением и 2-м, создайте DataTable и в цикле добавьте в него строки, затем используйте BulkCopy для загрузки к DB в одном соединении используйте это для помощи в массовой копии

еще одна вещь, чтобы обратить внимание, что массовая копия - очень чувствительная операция, которая почти каждая ошибка приведет к аннулированию копии, например, если вы объявите имя столбца в dataTable как "текст", а в БД его "Текст", это вызовет исключение, удачи.