web-dev-qa-db-fra.com

Quel est le meilleur moyen de charger d’énormes résultats dans la mémoire?

J'essaie de charger 2 énormes résultats (source et cible) provenant de différents SGBDR, mais le problème avec lequel je me bats est d'obtenir ces 2 énormes résultats mis en mémoire.

Vous trouverez ci-dessous les requêtes permettant d'extraire des données de la source et de la cible:

  Sql Server -  select Id as LinkedColumn,CompareColumn from Source order by LinkedColumn

  Oracle -select Id as LinkedColumn,CompareColumn from Target order by LinkedColumn

Records in Source: 12377200 

Enregistrements dans la cible: 12266800

Voici les approches que j'ai essayées avec quelques statistiques:

1) 2 approche du lecteur de données ouvert pour la lecture des données source et cible :

Total jobs running in parallel = 3

Time taken by Job1 = 01:47:25

Time taken by Job1 = 01:47:25

Time taken by Job1 = 01:48:32

There is no index on Id Column.

Major time is spend here :


var dr = command.ExecuteReader();

Problems : 
There are timeout issues also for which i have to kept `commandtimeout` to 

0(infinity) et c'est mauvais.

2) Méthode de lecture morceau par morceau pour la lecture des données source et cible:  

   Total jobs = 1
   Chunk size : 100000
   Time Taken : 02:02:48
   There is no index on Id Column.

3) Méthode de lecture morceau par morceau pour la lecture des données source et cible:

   Total jobs = 1
   Chunk size : 100000
   Time Taken : 00:39:40
   Index is present on Id column.

4) 2 approche du lecteur de données ouvert pour la lecture des données source et cible:

   Total jobs = 1
   Index : Yes
   Time: 00:01:43

5) 2 approche du lecteur de données ouvert pour la lecture des données source et cible:

   Total jobs running in parallel = 3
   Index : Yes
   Time: 00:25:12

J'observe que, bien qu'indexer sur LinkedColumn améliore les performances, le problème est que nous avons affaire à des tables de SGBDR tierces qui peuvent avoir un index ou non.

Nous aimerions garder le serveur de base de données aussi libre que possible afin que l’approche du lecteur de données ne semble pas une bonne idée car de nombreux travaux s’exécutent en parallèle, ce qui met beaucoup de pression sur le serveur de base de données dont nous ne voulons pas.

Par conséquent, nous voulons récupérer les enregistrements dans la mémoire de ma ressource de la source à la cible et effectuer 1 à 1 enregistrements en gardant le serveur de base de données libre.

Remarque: Je souhaite le faire dans mon application c # et ne souhaite pas utiliser SSIS ou Linked Server.

Mettre à jour :  

Source SQL Requête Temps d'exécution dans le studio de gestion de serveur SQL: 00:01:41

Durée d'exécution de la requête SQL cible dans le studio de gestion de serveur SQL: 00: 01: 40

Quel sera le meilleur moyen de lire d’énormes résultats en mémoire?

Code:  

static void Main(string[] args)
        {   
            // Running 3 jobs in parallel
             //Task<string>[] taskArray = { Task<string>.Factory.StartNew(() => Compare()),
        //Task<string>.Factory.StartNew(() => Compare()),
        //Task<string>.Factory.StartNew(() => Compare())
        //};
            Compare();//Run single job
            Console.ReadKey();
        }
public static string Compare()
        {
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();
            var srcConnection = new SqlConnection("Source Connection String");
            srcConnection.Open();
            var command1 = new SqlCommand("select Id as LinkedColumn,CompareColumn from Source order by LinkedColumn", srcConnection);
            var tgtConnection = new SqlConnection("Target Connection String");
            tgtConnection.Open();
            var command2 = new SqlCommand("select Id as LinkedColumn,CompareColumn from Target order by LinkedColumn", tgtConnection);
            var drA = GetReader(command1);
            var drB = GetReader(command2);
            stopwatch.Stop();
            string a = stopwatch.Elapsed.ToString(@"d\.hh\:mm\:ss");
            Console.WriteLine(a);
            return a;
        }
      private static IDataReader GetReader(SqlCommand command)
        {
            command.CommandTimeout = 0;
            return command.ExecuteReader();//Culprit
        }
6
ILoveStackoverflow

Il n'y a rien (à ma connaissance) plus rapide qu'un DataReader pour récupérer des enregistrements de base de données.

Travailler avec des bases de données volumineuses comporte des défis, lire 10 millions d’enregistrements en moins de 2 secondes est très bon.

Si vous voulez plus vite, vous pouvez:

  1. la suggestion de jdwend: 

Utilisez sqlcmd.exe et la classe Process pour exécuter une requête et placer les résultats dans un fichier csv, puis lisez le csv en c #. sqlcmd.exe est conçu pour archiver des bases de données volumineuses et s'exécute 100 fois plus rapidement que l'interface c #. L'utilisation de méthodes linq est également plus rapide que la classe SQL Client

  1. Mettez en parallèle vos requêtes et extrayez simultanément les résultats de la fusion: https://shahanayyub.wordpress.com/2014/03/30/how-to-load-large-dataset-in-datagridview/

  2. Le plus simple (et l’OMI le meilleur pour un SELECT * tous) consiste à lui lancer du matériel: https://blog.codinghorror.com/hardware-is-cheap-programmers-are-expensive/

Assurez-vous également que vous testez le matériel PROD, en mode édition, car cela pourrait fausser vos tests.

3
Jeremy Thompson

Si vous devez traiter des ensembles de résultats de base de données volumineux à partir de Java, vous pouvez opter pour JDBC pour vous donner le contrôle de bas niveau requis. Par contre, si vous utilisez déjà un ORM dans votre application, le retour à JDBC peut entraîner des difficultés supplémentaires. Vous perdriez des fonctionnalités telles que le verrouillage optimiste, la mise en cache, la récupération automatique lors de la navigation dans le modèle de domaine, etc. Heureusement, la plupart des ORM, comme Hibernate, ont quelques options pour vous aider. Bien que ces techniques ne soient pas nouvelles, vous avez le choix entre plusieurs possibilités.

Un exemple simplifié; Supposons que nous ayons une table (mappée à la classe "DemoEntity") avec 100.000 enregistrements. Chaque enregistrement consiste en une colonne unique (mappée sur la propriété "propriété" dans DemoEntity) contenant des données alphanumériques aléatoires d'environ ~ 2 Ko. La machine virtuelle Java est exécutée avec -Xmx250m. Supposons que 250 Mo est la mémoire maximale globale pouvant être affectée à la machine virtuelle Java sur notre système. Votre travail consiste à lire tous les enregistrements de la table, à effectuer certains traitements non spécifiés, puis à stocker le résultat. Nous supposerons que les entités résultant de notre opération en bloc ne sont pas modifiées

C'est un motif que j'utilise. Il récupère les données d'un jeu d'enregistrements particulier dans une instance System.Data.DataTable, puis ferme et dispose toutes les ressources non gérées dès que possible. Le modèle fonctionne également pour d'autres fournisseurs sous System.Data, notamment System.Data.OleDb, System.Data.SqlClient, etc. Je pense que le SDK client Oracle implémente le même modèle. 

// don't forget this using statements
using System.Data;
using System.Data.SqlClient;

// here's the code.
var connectionstring = "YOUR_CONN_STRING";
var table = new DataTable("MyData");
using (var cn = new SqlConnection(connectionstring))
{
    cn.Open();
    using (var cmd = cn.CreateCommand())
    {
        cmd.CommandText = "Select [Fields] From [Table] etc etc";
                          // your SQL statement here.
        using (var adapter = new SqlDataAdapter(cmd))
        {
            adapter.Fill(table);
        } // dispose adapter
    } // dispose cmd
    cn.Close();
} // dispose cn

foreach(DataRow row in table.Rows) 
{
    // do something with the data set.
}
0
Glenn Ferrie

J'ai eu une situation similaire il y a plusieurs années. Avant d’examiner le problème, il fallait 5 jours d’exploitation continue pour déplacer des données entre 2 systèmes à l’aide de SQL.

J'ai pris une approche différente.

Nous avons extrait les données du système source dans seulement un petit nombre de fichiers représentant un modèle de données aplati et avons organisé les données dans chaque fichier afin que tout se déroule naturellement dans l'ordre approprié lors de la lecture des fichiers.

J'ai ensuite écrit un programme Java qui traitait ces fichiers de données aplatis et produisait des fichiers de chargement de table individuels pour le système cible. Ainsi, par exemple, l'extrait source contenait moins d'une douzaine de fichiers de données du système source, ce qui représentait environ 30 à 40 fichiers de chargement pour la base de données cible.

Ce processus ne prendrait que quelques minutes et j'intégrais un audit complet et un rapport d'erreurs. Nous pouvions rapidement détecter les problèmes et les divergences dans les données source, les résoudre et réexécuter le processeur.

La pièce finale du puzzle était un utilitaire multi-thread que j'avais écrit qui effectuait un chargement en bloc parallèle sur chaque fichier de chargement dans la base de données Oracle cible. Cet utilitaire a créé un processus Java pour chaque table et a utilisé le programme de chargement de table en bloc d’Oracle pour transférer rapidement les données dans la base de données Oracle.

En fin de compte, le transfert SQL-SQL sur 5 jours de millions d'enregistrements s'est transformé en à peine 30 minutes en utilisant une combinaison des capacités de chargement en bloc de Java et d'Oracle. Et il n'y avait aucune erreur et nous avons comptabilisé chaque centime de chaque compte transféré entre systèmes.

Alors, pensez peut-être en dehors de la boîte SQL et utilisez Java, le système de fichiers et le chargeur en bloc d’Oracle. Et assurez-vous que votre fichier IO est en cours sur des disques durs SSD.

0
Russ Jackson

Je pense que je traiterais ce problème différemment.

Mais avant faisons quelques hypothèses:

  • Selon la description de votre question, vous obtiendrez des données de SQL Server et Oracle.
  • Chaque requête retournera un tas de données
  • Vous ne spécifiez pas à quoi sert l’obtention de toutes ces données en mémoire, ni leur utilisation.
  • Je suppose que les données que vous allez traiter vont être utilisées plusieurs fois et que vous ne répétez pas les deux requêtes plusieurs fois.
  • Et quoi que vous fassiez avec les données, il ne sera probablement pas affiché à l'utilisateur en même temps.

Ayant ces points de base, je traiterais les éléments suivants:

  • Pensez à ce problème en tant que traitement de données
  • Avoir une troisième base de données ou un autre endroit avec auxiliar Tables de base de données où vous pouvez stocker tout le résultat des 2 requêtes.
  • Pour éviter les délais, essayez d’obtenir les données à l’aide de la pagination (plusieurs milliers à la fois), puis sauvegardez-les dans ces tables de bases de données auxiliaires et non dans la mémoire RAM.
  • Dès que votre logique termine le chargement des données (migration d'importation), vous pouvez alors commencer à le traiter.
  • Le traitement des données est un point clé des moteurs de base de données, ils sont efficaces et ont évolué pendant de nombreuses années sans perdre de temps à réinventer la roue. Utilisez certaines procédures stockées pour "écraser/traiter/fusionner" des 2 tables auxiliaires en une seule. 
  • Maintenant que vous avez toutes les données "fusionnées" dans une 3ème table auxiliaire, vous pouvez maintenant l’utiliser pour afficher ou autre chose dont vous avez besoin.
0
Dryadwoods

Si vous voulez le lire plus rapidement, vous devez utiliser l'API d'origine pour obtenir les données plus rapidement. Évitez les frameworks tels que linq et utilisez DataReader. Essayez de vérifier si vous avez besoin de quelque chose comme une lecture sale (avec (nolock) sur un serveur SQL).

Si vos données sont très volumineuses, essayez de mettre en œuvre une lecture partielle. Quelque chose comme créer un index à vos données. Peut-être que vous pouvez mettre condition où date de - à jusqu'à tout sélectionné.

Après cela, vous devez envisager d'utiliser le threading dans votre système pour paralléliser le flux. En fait, 1 thread pour obtenir le job 1, un autre thread pour obtenir le job 2. Celui-ci vous fera gagner beaucoup de temps.

0
temmyraharjo

Mis à part les aspects techniques, je pense qu'il y a un problème plus fondamental ici.

select [...] order by LinkedColumn

J'observe que, bien que l'index sur LinkedColumn améliore les performances, le problème est que nous traitons avec des tables de SGBDR tierces qui peuvent avoir un index ou non.

Nous aimerions garder le serveur de base de données aussi libre que possible

Si vous ne pouvez pas vous assurer que la base de données possède un index basé sur une arborescence sur cette colonne, cela signifie que la base de données sera assez occupée à trier vos millions d'éléments. C'est lent et gourmand en ressources. Supprimez le order by dans l'instruction SQL et exécutez-le du côté de l'application pour obtenir des résultats plus rapidement et réduire la charge sur la base de données ... ou vous assurer que la base de données possède un tel index !!!

... selon que cette extraction est une opération courante ou rare, vous souhaiterez soit appliquer un index approprié dans la base de données, soit tout simplement extraire tout et trier le côté client.

0
dagnelies