2009-05-09 4 views
1

Ich schreibe ein Testprogramm mit Ruby und ActiveRecord, und es liest ein Dokument , die wie 6000 Wörter lang ist. Und dann Tally ich nur die Worte vonDie Aktualisierung der db 6000 Zeiten dauert einige Minuten?

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 

und so ist dieser Teil Schlaufen für 6000 mal ... und es dauert ein paar Minuten, um Lauf mindestens sqlite3 verwenden. Es ist normal? Ich habe erwartet, dass es innerhalb weniger Sekunden laufen könnte ... kann MySQL es viel beschleunigen?

Antwort

13

Mit 6000 Anrufe in die Datenbank schreiben, werden Sie Geschwindigkeitsprobleme sehen. Ich würde die verschiedenen Zählungen im Speicher speichern und einmal am Ende in der Datenbank speichern, nicht 6000 mal auf dem Weg.

+0

Schreiben sogar alle Datensätze am Ende, es trifft die Datenbank 6000 mal ... und dauerte eine ganze Weile. Ich dachte an mehr als 10, 15 Sekunden max. Wenn ich alle Daten in eine flache Datei schreibe, sollte es nur 1 oder 2 Sekunden dauern. kann ich sqlite3 nicht abschalten, um jedes Mal "erzwinge" in die db zu schreiben, sondern schreibe es einmal nach dem Einfügen von Datensätzen? –

+1

schauen Sie auf meine Antwort, mit Masseneinsatz sollte es beschleunigen. –

1

Welche Art von Datenbankverbindung verwenden Sie? Bei einigen Datenbanken können Sie eine direkte Verbindung herstellen, anstatt eine TCP-Netzwerkverbindung zu verwenden, die den Netzwerkstapel durchläuft. Mit anderen Worten, wenn Sie eine Internetverbindung herstellen und Daten auf diese Weise senden, kann dies die Arbeit verlangsamen.

Eine weitere Möglichkeit, die Leistung einer Datenbankverbindung zu verbessern, besteht darin, SQL-Anweisungen in einem einzigen Befehl zusammenzufassen.

Zum Beispiel eine einzelne 6000 Linie SQL-Statement, das wie dieses

"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'" 

und führen Sie das als einen einzigen Befehl aussieht, wird die Leistung viel besser sein. Standardmäßig hat MySQL eine 'Paketgröße' Grenze von 1 Megabyte, aber Sie können das in der Datei my.ini ändern, wenn Sie möchten.

Da Sie Ihre Datenbankaufrufe über ActiveRecord abstrahieren, haben Sie nicht viel Kontrolle darüber, wie die Befehle ausgegeben werden. Daher kann es schwierig sein, Ihren Code zu optimieren.

Eine andere dünne Sie könnte wäre, um eine Zählung der Wörter im Speicher zu halten, und dann nur die endgültige Summe in die Datenbank einfügen, anstatt dann ein Update jedes Mal, wenn Sie auf ein Wort stoßen. Das wird wahrscheinlich eine Menge an Beilagen einsparen, denn wenn Sie jedes Mal ein Update machen, wenn Sie auf das Wort "das" stoßen, ist das eine riesige, riesige Verschwendung. Wörter haben eine "Long Tail" -Distribution und die gebräuchlichsten Wörter sind sehr viel häufiger als obskurere Wörter. Dann würde der zugrunde liegenden SQL aussehen wie folgt aus:

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

Wenn Sie sich Sorgen machen über zu viel Speicher aufnehmen, können Sie Wörter zählen konnte und in regelmäßigen Abständen ‚Flush‘ sie. Lies also ein paar Megabyte Text, verbringe dann ein paar Sekunden damit, die Summen zu aktualisieren, anstatt jedes Wort jedes Mal zu aktualisieren, wenn du darauf stößt. Wenn Sie die Leistung verbessern wollen noch mehr, sollten Sie SQL-Befehle in Chargen Ausgabe betrachten direkt

+0

es verwendet sqlite3 ... und wenn ich activerecord benutze, sieht es so aus, als würde es die db und die Festplatte 6000 mal treffen und somit ziemlich langsam. also hoffe ich, alles auf einmal zu schreiben ... –

2

Ich schrieb ein paar schnelle Code in Perl-up, die einfach macht:

  1. Erstellen Sie die Datenbank
  2. einen Datensatz einfügen, die enthält nur eine einzige ganze Zahl
  3. Abrufen der letzten Aufzeichnung und stellen sie sicher, dass es zurückgibt, was es
  4. eingefügt

und es tut Schritte # 2 und # 3 6000 mal. Dies ist offensichtlich eine wesentlich geringere Arbeitslast als eine vollständige Objekt/relationale Brücke.Für diesen trivialen Fall mit SQLite dauerte es immer noch 17 Sekunden, um ausgeführt zu werden, also ist Ihr Wunsch, es "ein paar Sekunden" dauern zu lassen, auf "traditioneller Hardware" nicht realistisch.

Mit dem Monitor habe ich überprüft, dass es vor allem Festplattenaktivität war, die verlangsamt wurde. Basierend auf, dass, wenn aus irgendeinem Grund Sie wirklich die Datenbank tun müssen, um verhalten, dass schnell schlage ich eine von zwei Optionen:

  1. tun, was die Leute vorgeschlagen haben und finden sich rund um die Anforderung
  2. Versuchen Sie, einige Solid State Disks kaufen .

Ich denke, # 1 ist ein guter Weg zu beginnen :)

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

Moderne Datenbanken können 6k Einfügungen in einer Sekunde leicht behandeln, wenn sie richtig eingerichtet werden, mit den richtigen Verbindungstypen, Indexen, Disk-Caching, etc. –

1

Ohne zu wissen, über Ruby und SQLite, einige allgemeine Hinweise: einen eindeutigen Index

erstellen auf Word.s (Sie haben nicht angegeben, ob Sie einen haben)

Definieren Sie einen Standard für Word.count in der Datenbank (DEFAULT 1)

optimize Zuordnung Zahl:

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

Sie mit der Transaktion beginnen, bevor die Updates dann am Ende COMMIT.

0

ok, ich einige allgemeine Regel gefunden:

1) einen Hash verwenden, um die Zählung zuerst zu halten, nicht die db
2) am Ende, wickelt alle Einsatz oder Updates in einer einzigen Transaktion, so dass es wird die db 6000 mal nicht treffen.

Verwandte Themen