Sqlserver
 sql >> Base de Dados >  >> RDS >> Sqlserver

Como posso inserir 10 milhões de registros no menor tempo possível?


Por favor, não crie uma DataTable para carregar via BulkCopy. Essa é uma solução aceitável para conjuntos de dados menores, mas não há absolutamente nenhuma razão para carregar todas as 10 milhões de linhas na memória antes de chamar o banco de dados.

Sua melhor aposta (fora do BCP / BULK INSERT / OPENROWSET(BULK...) ) é transmitir o conteúdo do arquivo para o banco de dados por meio de um parâmetro com valor de tabela (TVP). Ao usar um TVP, você pode abrir o arquivo, ler uma linha e enviar uma linha até terminar e, em seguida, fechar o arquivo. Esse método tem um volume de memória de apenas uma única linha. Eu escrevi um artigo, Streaming Data Into SQL Server 2008 From an Application, que tem um exemplo desse cenário.

Uma visão geral simplista da estrutura é a seguinte. Estou assumindo a mesma tabela de importação e nome de campo, conforme mostrado na pergunta acima.

Objetos de banco de dados necessários:
-- 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

O código do aplicativo C# para fazer uso dos objetos SQL acima está abaixo. Observe como ao invés de preencher um objeto (por exemplo, DataTable) e então executar o Stored Procedure, neste método é a execução do Stored Procedure que inicia a leitura do conteúdo do arquivo. O parâmetro de entrada do Stored Proc não é uma variável; é o valor de retorno de um método, GetFileContents . Esse método é chamado quando o SqlCommand chama ExecuteNonQuery , que abre o arquivo, lê uma linha e envia a linha para o SQL Server por meio do IEnumerable<SqlDataRecord> e yield return constrói e, em seguida, fecha o arquivo. O procedimento armazenado apenas vê uma variável de tabela, @ImportTable, que pode ser acessada assim que os dados começarem a chegar (nota:os dados persistem por um curto período de tempo, mesmo que não seja o conteúdo completo, em 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();
   }
}

O GetFileContents método acima é usado como o valor do parâmetro de entrada para o procedimento armazenado conforme mostrado abaixo:
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;
}

Notas Adicionais:
  1. Com algumas modificações, o código C# acima pode ser adaptado para agrupar os dados.
  2. Com pequenas modificações, o código C# acima pode ser adaptado para enviar em vários campos (o exemplo mostrado no artigo "Steaming Data..." vinculado acima passa em 2 campos).
  3. Você também pode manipular o valor de cada registro no SELECT instrução no proc.
  4. Você também pode filtrar linhas usando uma condição WHERE no proc.
  5. Você pode acessar a variável de tabela TVP várias vezes; é READONLY, mas não "somente encaminhamento".
  6. Vantagens sobre SqlBulkCopy :
    1. SqlBulkCopy é somente INSERT, enquanto o uso de um TVP permite que os dados sejam usados ​​de qualquer maneira:você pode chamar MERGE; você pode DELETE com base em alguma condição; você pode dividir os dados em várias tabelas; e assim por diante.
    2. Devido a um TVP não ser somente INSERT, você não precisa de uma tabela de preparação separada para despejar os dados.
    3. Você pode obter dados do banco de dados chamando ExecuteReader em vez de ExecuteNonQuery . Por exemplo, se houver uma IDENTITY campo no DATAs import tabela, você pode adicionar um OUTPUT cláusula para o INSERT para retornar INSERTED.[ID] (assumindo ID é o nome da IDENTITY campo). Ou você pode repassar os resultados de uma consulta completamente diferente, ou ambos, pois vários conjuntos de resultados podem ser enviados e acessados ​​via Reader.NextResult() . Não é possível obter informações do banco de dados ao usar SqlBulkCopy ainda há várias perguntas aqui no S.O. de pessoas querendo fazer exatamente isso (pelo menos no que diz respeito ao recém-criado IDENTITY valores).
    4. Para obter mais informações sobre por que às vezes é mais rápido para o processo geral, mesmo que um pouco mais lento ao obter os dados do disco para o SQL Server, consulte este whitepaper da equipe de consultoria ao cliente do SQL Server:Maximizing Throughput with TVP
    5. >