web-dev-qa-db-fra.com

Moyen le plus rapide d'insérer 1 million de lignes dans SQL Server

J'écris une procédure stockée pour insérer des lignes dans une table. Le problème est que, dans certaines opérations, nous pouvons vouloir insérer plus de 1 million de lignes et nous voulons accélérer les choses. Une autre chose est que dans l'une des colonnes, il s'agit de Nvarchar(MAX). Nous pourrions vouloir mettre 1000 caractères en moyenne dans cette colonne.

Tout d'abord, j'ai écrit un prc pour insérer ligne par ligne. Ensuite, je génère des données aléatoires à insérer avec la colonne NVARCHAR(MAX) sous la forme d’une chaîne de 1000 caractères. Ensuite, utilisez une boucle pour appeler le prc pour insérer les lignes. La performance est très mauvaise, ce qui prend 48 minutes si j'utilise SQL Server pour ouvrir une session sur le serveur de base de données à insérer. Si j'utilise C # pour me connecter au serveur sur mon bureau (c'est ce que nous voulons généralement faire), cela prend plus de 90 minutes.

Ensuite, j'ai changé le prc pour prendre un paramètre de type table comme entrée. J'ai préparé les lignes d'une manière ou d'une autre et les ai placées dans le paramètre de type de table et faites l'insertion à l'aide de la commande suivante:

INSERT INTO tableA SELECT * from @tableTypeParameterB

J'ai essayé la taille de lot en tant que 1000 lignes et 3000 lignes (Mettez 1000-3000 lignes dans le @tableTypeParameterB pour être inséré pour une fois). La performance est toujours mauvaise. Il faut environ 3 minutes pour insérer 1 million de lignes si je l'exécute sur le serveur SQL et environ 10 minutes si j'utilise un programme C # pour me connecter à partir de mon bureau.

La tableA a un index clusterisé avec 2 colonnes.

Mon objectif est de faire l'insertion aussi vite que possible (l'objectif de mon idée est dans 1 min). Est-il possible de l'optimiser?


Juste une mise à jour:

J'ai essayé l'insertion de copie en bloc qui a été suggéré par certaines personnes ci-dessous. J'ai essayé d'utiliser SQLBULKCOPY pour insérer 1000 lignes et 10000 lignes à la fois. La performance est encore 10 minutes pour insérer 1 million de lignes (chaque ligne a une colonne de 1000 caractères). Il n'y a pas d'amélioration de la performance. Y a-t-il d'autres suggestions?


Une mise à jour basée sur les commentaires requis.

Les données proviennent en réalité de l'interface utilisateur. L'utilisateur modifiera l'utilisation de l'interface utilisateur pour sélectionner en gros, disons, un million de lignes et modifier une colonne de l'ancienne valeur à la nouvelle. Cette opération sera effectuée dans une procédure séparée.Mais ici, nous devons faire en sorte que le service de niveau intermédiaire récupère l'ancienne valeur et la nouvelle valeur de l'interface utilisateur et les insère dans la table. L'ancienne valeur et la nouvelle valeur peuvent comporter jusqu'à 4000 caractères et la moyenne 1000 caractères. Je pense que la longue chaîne ancienne/nouvelle valeur ralentit la vitesse car lorsque je modifie les données de test ancienne valeur/nouvelle valeur à 20-50 caractères et que l'insertion est très rapide, peu importe l'utilisation de SQLBulkCopy ou une variable de type table

9
Mandy

Je pense que ce que vous recherchez est Bulk Insert si vous préférez utiliser SQL.

Vous pouvez également utiliser l'option ADO.NET pour Batch Operations afin de conserver la logique dans votre application C #. Cet article est également très complet.

Mettre à jour

Oui, je crains que l'insertion en bloc ne fonctionne qu'avec les fichiers importés (de la base de données).

J'ai une expérience dans un projet Java où nous devions insérer des millions de lignes (les données venaient de l'extérieur de l'application).

La base de données étant Oracle, nous avons bien sûr utilisé l'insert multiligne d'Oracle. Il s'est avéré que la mise à jour par lot Java était beaucoup plus rapide que l'insertion à valeurs multiples d'Oracle (appelée "mises à jour en masse").

Ma suggestion est:

Si les données que vous allez manipuler proviennent de l'extérieur de votre application (si elles ne figurent pas déjà dans la base de données), je dirais qu'il suffit de rechercher les insertions par lots ADO.NET. Je pense que c'est votre cas.

Remarque: n'oubliez pas que les insertions de lot fonctionnent généralement avec la même requête. C'est ce qui les rend si rapides.

7
BonanzaOne

L'appel d'un prc dans une boucle entraîne de nombreux allers-retours vers SQL. 

Vous ne savez pas quelle méthode de traitement par lots vous avez utilisée, mais vous devriez vous pencher sur les paramètres de valeur de la table: Les documents sont ici . Vous voudrez encore écrire en lots. 

Vous voudrez également considérer la mémoire sur votre serveur. Le traitement par lots (par exemple, 10 Ko à la fois) peut être un peu plus lent, mais peut limiter la pression de la mémoire sur votre serveur, car vous mettez en mémoire tampon et traitez un jeu à la fois.

Les paramètres table-fournissent un moyen facile de marshaler plusieurs lignes des données d'une application client vers SQL Server sans nécessiter allers-retours multiples ou logique spéciale côté serveur pour le traitement du fichier Les données. Vous pouvez utiliser des paramètres table pour encapsuler des lignes de données dans une application client et envoyez les données au serveur en un seul fichier commande paramétrée. Les lignes de données entrantes sont stockées dans une table variable pouvant ensuite être utilisée à l’aide de Transact-SQL.

Une autre option est bulk insert . Les TVP bénéficient de la réutilisation mais cela dépend donc de votre modèle d'utilisation. Le premier lien contient une note sur la comparaison:

L'utilisation de paramètres table est comparable à d'autres méthodes d'utilisation de variables basées sur les ensembles; cependant, en utilisant fréquemment des paramètres de table peut être plus rapide pour les grands ensembles de données. Comparé aux opérations en vrac qui ont un coût de démarrage supérieur à celui des paramètres table, valeur table les paramètres fonctionnent bien pour l'insertion de moins de 1000 lignes.

Les paramètres table qui sont réutilisés bénéficient de la table temporaire la mise en cache. Cette mise en cache de table permet une meilleure évolutivité que son équivalent Opérations en bloc d'insertion.

Une autre comparaison ici: Performances de bcp/BULK INSERT par rapport aux paramètres table-Valeur

2
bryanmac

Voici un exemple de ce que j'ai déjà utilisé avec SqlBulkCopy. Accordez-moi, je ne m'occupais que d'environ 10 000 enregistrements, mais il les a insérés quelques secondes après l'exécution de la requête. Mes noms de champs étaient les mêmes, donc c'était assez facile. Vous devrez peut-être modifier les noms de champs DataTable. J'espère que cela t'aides.

private void UpdateMemberRecords(Int32 memberId)
    {

    string sql = string.Format("select * from Member where mem_id > {0}", memberId);
    try {
        DataTable dt = new DataTable();
        using (SqlDataAdapter da = new SqlDataAdapter(new SqlCommand(sql, _sourceDb))) {
            da.Fill(dt);
        }

        Console.WriteLine("Member Count: {0}", dt.Rows.Count);

        using (SqlBulkCopy sqlBulk = new SqlBulkCopy(ConfigurationManager.AppSettings("DestDb"), SqlBulkCopyOptions.KeepIdentity)) {
            sqlBulk.BulkCopyTimeout = 600;
            sqlBulk.DestinationTableName = "Member";
            sqlBulk.WriteToServer(dt);
        }
    } catch (Exception ex) {
        throw;
    }
}
0
Randy R