2009-05-09 4 views
1

J'écris un programme de test avec Ruby et ActiveRecord, et il lit un document qui est comme 6000 mots de long. Et puis je viens additionnez les mots parLa mise à jour de la db 6000 fois prendra quelques minutes?

recordWord = Word.find_by_s(word); 
if (recordWord.nil?) 
    recordWord = Word.new 
    recordWord.s = word 
end 
if recordWord.count.nil? 
    recordWord.count = 1 
else 
    recordWord.count += 1 
end 
recordWord.save 

et donc cette partie des boucles pour 6000 fois ... et il faut quelques minutes pour au moins terme en utilisant sqlite3. Est-ce normal? Je m'attendais à ce qu'il puisse courir en quelques secondes ... MySQL peut-il l'accélérer beaucoup?

Répondre

13

Avec 6000 appels à écrire dans la base de données, vous verrez des problèmes de vitesse. Je voudrais enregistrer les différents résultats en mémoire et les enregistrer dans la base de données une fois à la fin, pas 6000 fois en cours de route.

+0

même en écrivant tous les enregistrements à la fin, il frappe la base de données 6000 fois ... et a pris un certain temps. Je pensais à plus de 10, 15 secondes maximum. Si j'écris toutes les données dans un fichier plat, cela ne devrait prendre que 1 ou 2 secondes. ne puis-je désactiver sqlite3 pour ne pas "forcer l'écriture" sur la base de données à chaque fois, mais l'écrire une fois après tout l'insertion d'enregistrements? –

+1

regardez ma réponse, en utilisant l'encart en vrac devrait l'accélérer. –

1

Quel type de connexion à la base de données utilisez-vous? Certaines bases de données vous permettent de vous connecter «directement» plutôt que d'utiliser une connexion réseau TCP qui traverse la pile réseau. En d'autres termes, si vous établissez une connexion Internet et envoyez des données de cette manière, cela peut ralentir les choses.

Une autre façon d'améliorer les performances d'une connexion de base de données consiste à regrouper les instructions SQL en une seule commande.

Par exemple, faire une seule ligne 6000 instruction SQL qui ressemble à ceci

"update words set count = count + 1 where word = 'the' 
update words set count = count + 1 where word = 'in' 
... 
update words set count = count + 1 where word = 'copacetic'" 

et exécuter que comme une seule commande, la performance sera beaucoup mieux. Par défaut, MySQL a une limite de 'taille de paquet' de 1 mégaoctet, mais vous pouvez changer cela dans le fichier my.ini pour être plus grand si vous voulez. Comme vous supprimez vos appels de base de données via ActiveRecord, vous n'avez pas beaucoup de contrôle sur la façon dont les commandes sont émises. Il peut donc être difficile d'optimiser votre code.

Autre pourrait serait de garder un nombre de mots en mémoire, puis seulement insérer le total final dans la base de données, plutôt que de faire une mise à jour chaque fois que vous rencontrez un mot. Cela réduira probablement beaucoup le nombre d'insertions, parce que si vous faites une mise à jour chaque fois que vous rencontrez le mot «le», c'est un énorme, énorme gâchis. Les mots ont une distribution «longue queue» et les mots les plus courants sont énormément plus communs que les mots plus obscurs. Ensuite, le SQL sous-jacente ressemblerait plus à ceci:

"update words set count = 300 where word = 'the' 
update words set count = 250 where word = 'in' 
... 
update words set count = 1 where word = 'copacetic'" 

Si vous êtes inquiet au sujet de prendre trop de mémoire, vous pouvez compter les mots et périodiquement les « flush ». Lisez donc quelques mégaoctets de texte, puis passez quelques secondes à mettre à jour les totaux, plutôt que de mettre à jour chaque mot chaque fois que vous le rencontrez. Si vous souhaitez améliorer les performances encore plus, vous devriez envisager d'émettre des commandes SQL par lots directement

+0

il utilise sqlite3 ... et si j'utilise activerecord, on dirait qu'il va frapper le disque dur et le disque dur 6000 fois et donc assez lent. donc j'espère pouvoir tout écrire en même temps ... –

2

J'ai écrit un code rapide en Perl qui fait simplement:

  1. Créer la base de données
  2. Insérez un disque qui ne contient un seul entier
  3. Récupérer enregistrement le plus récent et vérifiez qu'il retourne ce qu'il a inséré

et il fait les étapes # 2 et # 3 6000 fois. C'est évidemment une charge de travail considérablement plus légère que d'avoir un objet entier/un pont relationnel.Pour ce cas trivial avec SQLite il a encore fallu 17 secondes pour s'exécuter, donc votre désir de l'avoir pris "quelques secondes" n'est pas réaliste sur "le matériel traditionnel". Utilisation du moniteur J'ai vérifié que c'était principalement l'activité du disque qui ralentissait le fonctionnement. Sur la base que si pour une raison quelconque, vous avez vraiment besoin de la base de données pour se comporter que je suggère rapidement l'une des deux options suivantes:

  1. faire ce que les gens ont suggéré et trouver loin autour de l'exigence
  2. Essayez d'acheter des disques d'état solide .

Je pense # 1 est une bonne façon de commencer :)

code:

#!/usr/bin/perl 

use warnings; 
use strict; 

use DBI; 

my $dbh = DBI->connect('dbi:SQLite:dbname=/tmp/dbfile', '', ''); 

create_database($dbh); 
insert_data($dbh); 

sub insert_data { 
    my ($dbh) = @_; 

    my $insert_sql = "INSERT INTO test_table (test_data) values (?)"; 
    my $retrieve_sql = "SELECT test_data FROM test_table WHERE test_data = ?"; 

    my $insert_sth = $dbh->prepare($insert_sql); 
    my $retrieve_sth = $dbh->prepare($retrieve_sql); 

    my $i = 0; 
    while (++$i < 6000) { 
    $insert_sth->execute(($i)); 
    $retrieve_sth->execute(($i)); 

    my $hash_ref = $retrieve_sth->fetchrow_hashref; 

    die "bad data!" unless $hash_ref->{'test_data'} == $i; 
    } 
} 

sub create_database { 
    my ($dbh) = @_; 

    my $status = $dbh->do("DROP TABLE test_table"); 
    # return error status if CREATE resulted in error 
    if (!defined $status) { 
    print "DROP TABLE failed"; 
    } 

    my $create_statement = "CREATE TABLE test_table (id INTEGER PRIMARY KEY AUTOINCREMENT, \n"; 
    $create_statement .= "test_data varchar(255)\n"; 
    $create_statement .= ");"; 

    $status = $dbh->do($create_statement); 

    # return error status if CREATE resulted in error 
    if (!defined $status) { 
    die "CREATE failed"; 
    } 
} 
+0

Les bases de données modernes peuvent facilement gérer les insertions 6k en une seconde si elles sont correctement configurées, avec les bons types de connexion, les index, la mise en cache disque, etc. –

1

Sans savoir à propos de Ruby et SQLite, quelques conseils généraux:

créer un index unique sur Word.s (vous n'avez pas indiqué si vous en avez un)

définir une valeur par défaut pour Word.count dans la base de données (DEFAUT 1)

affectation optimize du comte:

recordWord = Word.find_by_s(word); 
if (recordWord.nil?) 
    recordWord = Word.new 
    recordWord.s = word 
    recordWord.count = 1 
else 
    recordWord.count += 1 
end 
recordWord.save 
1

Utilisez BEGIN TRANSACTION avant vos mises à jour COMMIT puis à la fin.

0

ok, je l'ai trouvé une règle générale:

1) utiliser un hachage pour garder le premier chef d'accusation, et non le db
2) à la fin, envelopper toutes les insertions ou les mises à jour dans une transaction, de sorte que ne frappera pas le db 6000 fois.

Questions connexes