Helmut Weber: MySQL Geschwindigkeits-Probleme

Hallo SelfForum,

ich möchte bei einer Gewinnspiel-Datenbank auswerten, in welchem Monat wieviele Besucher bei dem Gewinnspiel mitgemacht haben.
Die Tabelle umfasst über 600.000 Einträge und ist wie folgt aufgebaut:

CREATE TABLE participant (
  answer varchar(255) NOT NULL default '',
  surname varchar(255) NOT NULL default '',
  name varchar(255) NOT NULL default '',
  email varchar(255) NOT NULL default '',
  address varchar(255) NOT NULL default '',
  code varchar(255) NOT NULL default '',
  city varchar(255) NOT NULL default '',
  age int(2) NOT NULL default '0',
  part_id int(11) NOT NULL auto_increment,
  entry timestamp(14) NOT NULL,
  PRIMARY KEY  (part_id)
) TYPE=MyISAM COMMENT='Teilnehmer Gewinnspiel';

In Perl werte ich jetzt die Teilnehmer für dieses Jahr wie folgt aus:

my $year = '2003';

my $entrys_year = $Config::DBH->selectrow_array( "SELECT COUNT(part_id) FROM participant WHERE year(entry) = $year" );

my $entrys_januar = $Config::DBH->selectrow_array( "SELECT COUNT(part_id) FROM participant WHERE YEAR(entry) = $year AND MONTH(entry) = 1" );
my $entrys_februar = $Config::DBH->selectrow_array( "SELECT COUNT(part_id) FROM participant WHERE YEAR(entry) = $year AND MONTH(entry) = 2" );
.
.
.... bis "12"

Es Dauert verdammt lange, bis sich die Ergebnisse zu sehen bekomme (Wenn mir sogar ein Server-Timeout nicht dazwischen kommt).

Ich habe auch schon mal die Variante ausprobiert, zuerst alle Zeilen einzulesen, und dann in Perl in die verschiedenen Monate auszuwerten, jedoch ohne nennenswerten Geschwindigkeits-Erfolg.

my $sth = $Config::DBH->prepare( "SELECT date_format(entry, '%m') FROM participant WHERE YEAR(entry) = $year" );
 $sth->execute();
 my $eintraege = $sth->fetchall_arrayref();
 foreach my $item (@$eintraege ) {
  my ( $month ) = @$item;
  if ( $month eq "01" ) { $entrys_januar++ }
  elsif ( $month eq "02" ) { $entrys_februar++ }
  elsif ( $month eq "03" ) { $entrys_maerz++ }
  elsif ( $month eq "04" ) { $entrys_april++ }
  elsif ( $month eq "05" ) { $entrys_mai++ }
  elsif ( $month eq "06" ) { $entrys_juni++ }
  elsif ( $month eq "07" ) { $entrys_juli++ }
  elsif ( $month eq "08" ) { $entrys_august++ }
  elsif ( $month eq "09" ) { $entrys_september++ }
  elsif ( $month eq "10" ) { $entrys_september++ }
  elsif ( $month eq "11" ) { $entrys_november++ }
  elsif ( $month eq "12" ) { $entrys_dezember++ }
  $entrys_year++;
 }

Auch einen Index über die Spalte "part_id" und "entry" hat keinen Erfolg gebracht.

Hat jemand für mich einen Tipp, wie ich die Tabelle in einem vernünftigen Tempo auswerten kann?

Ich habe auch schon mit dem Modul Time::HiRes eine Zeit gleich zu Beginn und zum Ende des Skriptes gemessen und die Differenz darstellen lassen (Wie im Buch "CGI Programmierung mit Perl" beschrieben). Komischerweise habe ich einen Zeitwert immer im Millisekunden-Bereich gehabt. Mir kam es so vor, das die Zeit, in der die Datenbank beschäftigt war, gar nicht mit gemessen wurde.

Kennt jemand eine verlässliche Methode, um die Laufzeit eines Perl-Skriptes zu bestimmen?

Gruß
Helmut Weber

--
-------------------------------------------
Mode ist eine Variable, Stil eine Konstante
  1. Hallo SelfForum,

    ich möchte bei einer Gewinnspiel-Datenbank auswerten, in welchem Monat wieviele Besucher bei dem Gewinnspiel mitgemacht haben.
    Die Tabelle umfasst über 600.000 Einträge und ist wie folgt aufgebaut:

    CREATE TABLE participant (
      answer varchar(255) NOT NULL default '',
      surname varchar(255) NOT NULL default '',
      name varchar(255) NOT NULL default '',
      email varchar(255) NOT NULL default '',
      address varchar(255) NOT NULL default '',
      code varchar(255) NOT NULL default '',
      city varchar(255) NOT NULL default '',
      age int(2) NOT NULL default '0',
      part_id int(11) NOT NULL auto_increment,
      entry timestamp(14) NOT NULL,
      PRIMARY KEY  (part_id)
    ) TYPE=MyISAM COMMENT='Teilnehmer Gewinnspiel';

    In Perl werte ich jetzt die Teilnehmer für dieses Jahr wie folgt aus:

    my $year = '2003';

    my $entrys_year = $Config::DBH->selectrow_array( "SELECT COUNT(part_id) FROM participant WHERE year(entry) = $year" );

    my $entrys_januar = $Config::DBH->selectrow_array( "SELECT COUNT(part_id) FROM participant WHERE YEAR(entry) = $year AND MONTH(entry) = 1" );
    my $entrys_februar = $Config::DBH->selectrow_array( "SELECT COUNT(part_id) FROM participant WHERE YEAR(entry) = $year AND MONTH(entry) = 2" );
    .
    .
    .... bis "12"

    Es Dauert verdammt lange, bis sich die Ergebnisse zu sehen bekomme (Wenn mir sogar ein Server-Timeout nicht dazwischen kommt).

    Ich habe auch schon mal die Variante ausprobiert, zuerst alle Zeilen einzulesen, und dann in Perl in die verschiedenen Monate auszuwerten, jedoch ohne nennenswerten Geschwindigkeits-Erfolg.

    my $sth = $Config::DBH->prepare( "SELECT date_format(entry, '%m') FROM participant WHERE YEAR(entry) = $year" );
     $sth->execute();
     my $eintraege = $sth->fetchall_arrayref();
     foreach my $item (@$eintraege ) {
      my ( $month ) = @$item;
      if ( $month eq "01" ) { $entrys_januar++ }
      elsif ( $month eq "02" ) { $entrys_februar++ }
      elsif ( $month eq "03" ) { $entrys_maerz++ }
      elsif ( $month eq "04" ) { $entrys_april++ }
      elsif ( $month eq "05" ) { $entrys_mai++ }
      elsif ( $month eq "06" ) { $entrys_juni++ }
      elsif ( $month eq "07" ) { $entrys_juli++ }
      elsif ( $month eq "08" ) { $entrys_august++ }
      elsif ( $month eq "09" ) { $entrys_september++ }
      elsif ( $month eq "10" ) { $entrys_september++ }
      elsif ( $month eq "11" ) { $entrys_november++ }
      elsif ( $month eq "12" ) { $entrys_dezember++ }
      $entrys_year++;
     }

    warum machst Du das so?
    Ich würde das in EINER Abfrage machen, d.h. mittels group by month.
    Schleifen sollte man generell minimieren.

    Gruß
    Reiner

    1. Hallo Reiner,

      warum machst Du das so?
      Ich würde das in EINER Abfrage machen, d.h. mittels group by month.
      Schleifen sollte man generell minimieren.

      Reine Unwissenheit ;)

      Ich habe mal folgendes Ausprobiert:
      SELECT MONTH(entry), COUNT(part_id) as Anzahl
      FROM paarticipant
      GROUP BY MONTH(entry);

      Jetzt muss ich das ganze mal in mein Skript einbinden, und mal schauen ob ich eine Geschwindigkeits-Verbesserung erreicht habe.

      Für den Tipp aber schon einmal meinen Dank.

      Gruß
      Helmut Weber

      --
      -------------------------------------------
      Mode ist eine Variable, Stil eine Konstante
  2. Hi Helmut Weber,

    Es Dauert verdammt lange, bis sich die Ergebnisse zu sehen bekomme (Wenn mir sogar ein Server-Timeout nicht dazwischen kommt).
    Auch einen Index über die Spalte "part_id" und "entry" hat keinen Erfolg gebracht.

    das ist der Preis dafür, daß Du "year()" verwendest - damit machst Du Dir den Indexzugriff kaputt. Der Index über "entry" wäre nämlich die richtige Idee gewesen.

    Würdest Du den "entry"-Wert mit BETWEEN abfragen, dann könntest Du von diesem Index profitieren - weil dann beim Spaltenzugriff keine Funktion mehr aufgerufen werden müßte. (EXPLAIN hätte Dir das übrigens auch gezeigt.)
    (Bei den konstanten Vergleichswerten für den 1. Januar und den 31. Dezember vermutlich schon - irgendwas in Richtung to_date(), vermute ich mal, ich habe die Syntax gerade nicht zur Hand - aber das geschieht dann nur ein einziges Mal während der Codegenerierung.)

    Daß dies dann aber wirklich schnell wird, bezweifele ich - weil der Index sehr wahrscheinlich zu schlecht projeziert.
    Ein Index bringt dann etwas, wenn Du eine _kleine_ Teilmenge der Tabelle herausholen willst - die Grenze liegt irgendwo in der Größenordnung von 5-10%.
    Das würde bedeuten, daß Dein Datenbestand mindestens 10-20 Jahre tief sein muß ...

    Stelle Dich geistig darauf ein, daß Dein Problem mit Deiner existierenden Datenstruktur nicht schneller als mit einem full table scan zu lösen ist.

    Viele Grüße
          Michael

    --
    T'Pol: I apologize if I acted inappropriately.
    V'Lar: Not at all. In fact, your bluntness made me reconsider some of my positions. Much as it has now.
    (sh:| fo:} ch:] rl:( br:^ n4:( ie:% mo:) va:| de:/ zu:| fl:( ss:) ls:~ js:|)
    Auch diese Signatur wird an korrekt konfigurierte Browser gzip-komprimiert übertragen.