web-dev-qa-db-fra.com

Insérer rapidement 2 millions de lignes dans SQL Server

Je dois insérer environ 2 millions de lignes d'un fichier texte.

Et avec l'insertion, je dois créer des tables maîtres. 

Quel est le moyen le plus simple et le plus rapide d’insérer un aussi vaste ensemble de données dans SQL Server?

57
Wadhawan Vishal

Vous pouvez essayer avec SqlBulkCopy class.

Vous permet de charger efficacement en vrac une table SQL Server avec les données de une autre source.

Il y a un article de blog blog sur la façon de l'utiliser.

44
Soner Gönül
  1. Je pense qu'il vaut mieux que vous lisiez les données d'un fichier texte dans DataSet 

  2. Essayez SqlBulkCopy - Insertion en bloc dans SQL à partir de l'application C #

    // connect to SQL
    using (SqlConnection connection = 
            new SqlConnection(connString))
    {
        // make sure to enable triggers
        // more on triggers in next post
        SqlBulkCopy bulkCopy = 
            new SqlBulkCopy
            (
            connection, 
            SqlBulkCopyOptions.TableLock | 
            SqlBulkCopyOptions.FireTriggers | 
            SqlBulkCopyOptions.UseInternalTransaction,
            null
            );
    
        // set the destination table name
        bulkCopy.DestinationTableName = this.tableName;
        connection.Open();
    
        // write the data in the "dataTable"
        bulkCopy.WriteToServer(dataTable);
        connection.Close();
    }
    // reset
    this.dataTable.Clear();
    

ou 

après avoir fait l'étape 1 en haut

  1. Créer du XML à partir de DataSet 
  2. Passer du XML à la base de données et faire une insertion en bloc 

vous pouvez consulter cet article pour plus de détails: Insertion en bloc de données à l'aide de C # DataTable et de la fonction OpenXML du serveur SQL

Mais ce n’est pas testé avec 2 millions d’enregistrements, mais consomme de la mémoire sur la machine car vous devez charger 2 millions d’enregistrements et les insérer.

57
Pranay Rana

Re la solution pour SqlBulkCopy:

J'ai utilisé le StreamReader pour convertir et traiter le fichier texte. Le résultat était une liste de mon objet. 

J'ai créé une classe qui prend Datatable ou un List<T> et une taille de tampon (CommitBatchSize). Il convertira la liste en une table de données en utilisant une extension (dans la deuxième classe).

Ça marche très vite. Sur mon PC, je peux insérer plus de 10 millions d'enregistrements compliqués en moins de 10 secondes.

Voici la classe:

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DAL
{

public class BulkUploadToSql<T>
{
    public IList<T> InternalStore { get; set; }
    public string TableName { get; set; }
    public int CommitBatchSize { get; set; }=1000;
    public string ConnectionString { get; set; }

    public void Commit()
    {
        if (InternalStore.Count>0)
        {
            DataTable dt;
            int numberOfPages = (InternalStore.Count / CommitBatchSize)  + (InternalStore.Count % CommitBatchSize == 0 ? 0 : 1);
            for (int pageIndex = 0; pageIndex < numberOfPages; pageIndex++)
                {
                    dt= InternalStore.Skip(pageIndex * CommitBatchSize).Take(CommitBatchSize).ToDataTable();
                BulkInsert(dt);
                }
        } 
    }

    public void BulkInsert(DataTable dt)
    {
        using (SqlConnection connection = new SqlConnection(ConnectionString))
        {
            // make sure to enable triggers
            // more on triggers in next post
            SqlBulkCopy bulkCopy =
                new SqlBulkCopy
                (
                connection,
                SqlBulkCopyOptions.TableLock |
                SqlBulkCopyOptions.FireTriggers |
                SqlBulkCopyOptions.UseInternalTransaction,
                null
                );

            // set the destination table name
            bulkCopy.DestinationTableName = TableName;
            connection.Open();

            // write the data in the "dataTable"
            bulkCopy.WriteToServer(dt);
            connection.Close();
        }
        // reset
        //this.dataTable.Clear();
    }

}

public static class BulkUploadToSqlHelper
{
    public static DataTable ToDataTable<T>(this IEnumerable<T> data)
    {
        PropertyDescriptorCollection properties =
            TypeDescriptor.GetProperties(typeof(T));
        DataTable table = new DataTable();
        foreach (PropertyDescriptor prop in properties)
            table.Columns.Add(prop.Name, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType);
        foreach (T item in data)
        {
            DataRow row = table.NewRow();
            foreach (PropertyDescriptor prop in properties)
                row[prop.Name] = prop.GetValue(item) ?? DBNull.Value;
            table.Rows.Add(row);
        }
        return table;
    }
}

}

Voici un exemple lorsque je veux insérer une liste de mon objet personnalisé List<PuckDetection> (ListDetections):

var objBulk = new BulkUploadToSql<PuckDetection>()
{
        InternalStore = ListDetections,
        TableName= "PuckDetections",
        CommitBatchSize=1000,
        ConnectionString="ENTER YOU CONNECTION STRING"
};
objBulk.Commit();

La classe BulkInsert peut être modifiée pour ajouter un mappage de colonne si nécessaire. Exemple, vous avez une clé d’identité en tant que première colonne (en supposant que les noms de colonne du datatable soient les mêmes que ceux de la base de données).

//ADD COLUMN MAPPING
foreach (DataColumn col in dt.Columns)
{
        bulkCopy.ColumnMappings.Add(col.ColumnName, col.ColumnName);
}
13
Amir

Je me suis récemment heurté à ce scénario (bien plus de 7 millions de lignes) et à l'utilisation de sqlcmd via powershell (après l'analyse de données brutes dans des instructions d'insertion SQL) par segments de 5 000 éléments à la fois (SQL ne peut pas gérer 7 millions de lignes en une seule tâche ou même 500 000 lignes, sauf si elles sont décomposées en éléments plus petits de 5 Ko. Vous pouvez ensuite exécuter chaque script 5K l'un après l'autre.) car je devais tirer parti de la nouvelle commande de séquence dans SQL Server 2012 Enterprise. Je ne pouvais pas trouver de méthode de programmation pour insérer sept millions de lignes de données rapidement et efficacement avec ladite commande de séquence.

Deuxièmement, l'une des choses à surveiller lors de l'insertion d'un million de lignes ou plus de données en une seule séance est la consommation de CPU et de mémoire (principalement de la mémoire) pendant le processus d'insertion. SQL consommera de la mémoire/du processeur avec un travail de cette ampleur sans libérer lesdits processus. Inutile de dire que si votre serveur ne dispose pas de suffisamment de puissance de traitement ou de mémoire, vous pouvez le faire planter assez facilement en peu de temps (ce que j’ai découvert à la dure). Si vous arrivez au point où votre consommation de mémoire est supérieure à 70-75%, redémarrez simplement le serveur et les processus seront remis à l'état normal. 

Avant de pouvoir avoir un plan d'exécution final, je devais exécuter de nombreux essais et tests d'erreurs pour connaître les limites de mon serveur (compte tenu des ressources CPU/mémoire limitées avec lesquelles travailler). Je vous suggérerais de faire la même chose dans un environnement de test avant de le mettre en production.

3
Techie Joe

J'utilise l'utilitaire bcp. (Programme de copie en bloc) Je charge environ 1,5 million d’enregistrements de texte chaque mois . Chaque enregistrement de texte mesure 800 caractères de large . Table SQL Server.

Les instructions pour bcp sont à http://msdn.Microsoft.com/en-us/library/ms162802.aspx

3
Bill Edmett

J'ai essayé avec cette méthode et cela a considérablement réduit le temps d'exécution de l'insertion de ma base de données.

List<string> toinsert = new List<string>();
StringBuilder insertCmd = new StringBuilder("INSERT INTO tabblename (col1, col2, col3) VALUES ");

foreach (traverse your loop here)
{
      toinsert.Add(string.Format("( '{0}', '{1}', '{2}' )", "Val1", "Val2", "Val3"));
}
if (toinsert.Count != 0)
{
      insertCmd.Append(string.Join(",", toinsert));
      insertCmd.Append(";");
}
using (MySqlCommand myCmd = new MySqlCommand(insertCmd.ToString(), SQLconnectionObject))
{
      myCmd.CommandType = CommandType.Text;
      myCmd.ExecuteNonQuery();
}

* Créez un objet de connexion SQL et remplacez-le là où j'ai écrit SQLconnectionObject.

1
Amey Vartak