Komplexe Blätterfunktion bei > 10.000 Datensätzen

Mik3e

Erfahrenes Mitglied
Hi zusammen,

Ich weiß, es gab hier schon einige Threads zum Thema "Datensätze durchblättern", allerdings waren diese sowie das Tut nicht wirklich befriedigend.

Hier das "Pflichtenheft":

1. Daten
Die Daten werden aus einem SQL Query generiert (die Datenstruktur ist in diesem Fall egal :)). Der Resultset beinhaltet > 10.000 Datensätze, also ne ganze Menge.

2. Layout der Blätterfunktion:
Der User hat folgende Möglichkeiten:
a.) User kann einstellen, wie viele Datensätze pro Seite angezeigt werden
b.) User kann jeweils eine Seite vor/zurück sowie direkt zur ersten oder letzten Seite springen.
c.) User kann in einem Drop Down Feld eine bestimmte Seite direkt annavigieren.
d.) Der User sieht, auf welcher Seite er sich gerade befindet (z.B.: "Seite 3 von 203")

Alle Entwickler die mit PHPMyAdmin arbeiten, haben die Ähnlichkeit sicher erkannt :)

Nun stellen sich mehrere Fragen:

1. Daten in Session-Array packen?
Ist es clever, den gesamten Datenstock zuerst in einem Session-Array einzulesen, und dann die Ausgabe über Array Funktionen steuern?
Vorteile:
Ich kenne von Anfang an die Gesamt-Anzahl an Datensätzen und muss nur einmal die Daten auslesen (bessere SQL Performance).
Nachteil:
Ändern sich die Stammdaten während der User blättert, sieht er nicht die aktualisierten Daten (da diese ja nicht beim Blättern neu geladen, sondern aus dem Session Array ausgelesen werden).

2. Daten beim Blättern neu abfragen?
Die zweite Möglichkeit wäre, die Daten bei jedem Seitenaufruf neu aus der DB abzufragen und mit LIMIT im SQL Query zu arbeiten.
Hat den Vorteil, dass die Daten immer aktuell sind (im Gegensatz zum Session Array) und die eigentliche Datenabfrage dank LIMIT relativ schnell ist.
Nachteil:
Ich muss zusätzlich eine zweite Abfrage bei jedem Seitenaufruf implementieren, die mir die Gesamtanzahl an Datensätzen ausliest (da ansonsten die ganzen Berechnungen nicht möglich sind. z.B: Seite n von m kann man ohne die Gesamtanzahl an Datensätzen zu kennen nicht anzeigen.)

Mir ist klar, dass beide Varianten technisch möglich sind. Ich habe auch schon mehrere Blätter-Funktionen implementiert, nur habe ich den "Stein der Weisen" bisher nicht wirklich gefunden.

Vielleicht hat jemand von Euch Erfahrungswerte und kann mir diese mitteilen :)

Danke & LG
Mike
 
Ich denke es ist okay die Daten erneut abzufragen.
Ich hab vor kurzem das folgende kleine Benchmark-Script gebastelt, und eine Abfrage mit 5000 Datensaetzen dauert dabei weit weniger als eine Sekunde.
bench.php hat gesagt.:
Write 5000 rows 0.422621011734
Query Table (5000 rows) 0.0596659183502
Read 5000 rows 0.0194840431213

bench.php
PHP:
<?php
$host="";
$user="";
$password="";
$dir="";
function calctime($start,$end)
{
    list($msec,$sec)=explode(' ',$start);
    $start=(float)$msec+(float)$sec;
    list($msec,$sec)=explode(' ',$end);
    $end=(float)$msec+(float)$sec;
    $time=($end-$start);
    return $time;
}

function loop_bench($count=50000)
{
    echo '<tr><th colspan="2">Loops</th></tr>';
    $start=microtime();
    for ($x=0;$x<$count;$x++)
        {
            $b=$x;
        }
    $end=microtime();
    echo '<tr><td>FOR-Loop from 1 to '.$count.'</td><td>'.calctime($start,$end).'</td></tr>';
    $start=microtime();
    $x=0;
    while ($x<$count)
        {
            $x++;
        }
    $end=microtime();
    echo '<tr><td>WHILE-Loop from 1 to '.$count.'</td><td>'.calctime($start,$end).'</td></tr>';
}

function sql_bench($host,$username,$password,$rows=5000)
{
    echo '<tr><th colspan="2">MySQL</th></tr>';
    $db=mysql_connect($host,$username,$password);
    $start=microtime();
    mysql_query("create database benchtestdb",$db);
    $end=microtime();
    echo '<tr><td>Create database</td><td>'.calctime($start,$end).'</td></tr>';
    $start=microtime();
    mysql_select_db("benchtestdb",$db);
    $end=microtime();
    echo '<tr><td>Select database</td><td>'.calctime($start,$end).'</td></tr>';
    $start=microtime();
    mysql_query("create table benchtest (a INT(8) UNSIGNED NOT NULL,b INT(8) UNSIGNED NOT NULL,c INT(8) UNSIGNED NOT NULL,d INT(8) UNSIGNED NOT NULL,e INT(8) UNSIGNED NOT NULL)",$db);
    $end=microtime();
    echo '<tr><td>Create table</td><td>'.calctime($start,$end).'</td></tr>';
    $start=microtime();
    for ($x=0;$x<$rows;$x++)
        {
            mysql_query("insert into benchtest (a,b,c,d,e) values ('$x','$x','$x','$x','$x')",$db);
        }
    $end=microtime();
    echo '<tr><td>Write '.$rows.' rows</td><td>'.calctime($start,$end).'</td></tr>';
    $start=microtime();
    $result=mysql_query("select * from benchtest",$db);
    $end=microtime();
    echo '<tr><td>Query Table ('.$rows.' rows)</td><td>'.calctime($start,$end).'</td></tr>';
    $start=microtime();
    while ($row=mysql_fetch_assoc($result))
        {
            $b=$row;
        }
    $end=microtime();
    echo '<tr><td>Read '.$rows.' rows</td><td>'.calctime($start,$end).'</td></tr>';
    $start=microtime();
    mysql_query("drop database benchtestdb",$db);
    $end=microtime();
    echo '<tr><td>Drop database</td><td>'.calctime($start,$end).'</td></tr>';
    mysql_close($db);
}

function fs_bench($path,$bytes=50000,$dirs=1000)
{
    echo '<tr><th colspan="2">Files</th></tr>';
    $file=fopen($path."/benchtest.tmp","w");
    $start=microtime();
    for ($x=0;$x<$bytes;$x++)
        {
            fwrite($file,'x');
        }
    $end=microtime();
    echo '<tr><td>Write '.$bytes.' Bytes</td><td>'.calctime($start,$end).'</td></tr>';
    fclose($file);
    $file=fopen($path."/benchtest.tmp","r");
    $start=microtime();
    while (!feof($file))
        {
            $x=fread($file,1);
        }
    $end=microtime();
    echo '<tr><td>Read '.$bytes.' Bytes</td><td>'.calctime($start,$end).'</td></tr>';
    fclose($file);
    $start=microtime();
    unlink($path."/benchtest.tmp");
    $end=microtime();
    echo '<tr><td>Unlink file</td><td>'.calctime($start,$end).'</td></tr>';
    $start=microtime();
    for ($x=0;$x<$dirs;$x++)
        {
            mkdir($path."/benchtest".$x);
        }
    $end=microtime();
    echo '<tr><td>Create '.$dirs.' directories</td><td>'.calctime($start,$end).'</td></tr>';
    $start=microtime();
    for ($x=0;$x<$dirs;$x++)
        {
            rmdir($path."/benchtest".$x);
        }
    $end=microtime();
    echo '<tr><td>Remove '.$dirs.' directories</td><td>'.calctime($start,$end).'</td></tr>';    
}

echo 'PHP '.phpversion().'<br>';
echo '<table border="1">';
$start=microtime();
loop_bench();
sql_bench($host,$user,$password);
fs_bench($dir);
$end=microtime();
echo '</table>';
echo 'Total benchmark: '.calctime($start,$end);
?>
Die 4 Variablen am Anfang des Scripts muessen natuerlich angepasst werden.
Ausserdem koennen auch Teile des Benchmarks auskommentiert werden, sodass z.B. nur der SQL-Benchmark ablaeuft. Dafuer gibt's ja Funktionen. ;)
 
Es ist nur leider so, dass (wenn der Server unter Last gefahren wird) der COUNT Query ziemlich schleicht... Noch dazu kommen komplexe Joins dazu, da der User auch eine Suchfunktion zur Verfügung hat. Und der Count muss dann natürlich die Anzahl an Datensätzen aufgrund der Suche ermitteln.

D.h. ich muss bei jeder Seite zwei nahezu identische Queries absetzen (1x zum Zählen der Datensätze ohne LIMIT, und einmal zur Ausgabe mit LIMIT).

So habe ich bisher immer gearbeitet, ist aber nicht wirklich befriedigend gewesen...
Und mit einem Session-Array habe ich es bisher noch nie versucht. Ich hab auch leider keine Ahnung, ob der Webserver nicht in die Knie geht, wenn er nen Session-Array mit 10.000 Datensätzen speichern soll :)
 
Die Session-Daten werden ja in einer Datei auf dem Server gespeichert, da ist dann die Frage ob es da ein Limit fuer die Dateigroesse gibt. Bei mehr als 10000 Eintraegen wird sich die Geschichte ja schon etwas aufblaehen.
 
1. Dürfte schon aus servertechnischen Gründen wegfallen.
Ich glaube nicht, das es möglich ist, den Inhalt von 10000 Datensätzen
dateibasiert in einer Session zu speichern(Cookies fallen ohnehin weg, und die Session-Dateien dürften auch ein Limit haben.)
Auch dürftest du ziemlich sicher das Speicherlimit von PHP überschreiten.

Abgesehen davon... die Daten aus einer Datei zu holen dürfte kaum schonender sein, als sie aus einer DB abzufragen.
 
Ja, da hast Du natürlich recht.. File Zugriffe sind ja prinzipiell nicht sonderlich schnell.. Nur war ich mir nicht sicher, ob er Sessions nicht zusätlich auch im RAM hält (was die Sache natürlich wesentlich beschleunigen würde).

Wäre bei der Anzahl an Daten aber auch ein Problem :) Wo ist mein Server mit 2 Terabyte RAM *g*

Kann ja nicht sein, dass es nicht eine optimale Lösung für das Problem gibt... Ist ja nicht gerade ne ausgefallene Funktion...

Man müsste man den PHPMyAdmin zerlegen und nachsehen, wie es dort gelöst ist.
Was mich an Variante zwei extrem stört sind die zwei beinahe identischen SQL Abfragen.... Das ist Performancevergeudung hoch 3...

Da muss es doch noch ne andere Lösung für geben....
 
PHPMyAdmin zu zerlegen ist ein riesen Spass.
Ich hab mich da mal durchgewuehlt um zu finden wie ich feststelle ob eine Tabelle Overhead hat.
Das war echt anstrengend. Der Code ist so verwirrend, das glaubt man kaum.
 
Naja..die doppelten Queries... die Anzahl der Datensätze könntest du ja in einer Session speichern, das erspart dir, dies jedesmal abzufragen.

Wenn jemand zur letzten Seite blättert, kannst du die Anzahl ja neu ermitteln.
Andernfalls hat er nur eine u.U. nicht korrekte Seitenanzahl in der Anzeige, was man verkraften kann(finde ich:-))
 
Ahh.. Pfusch :)
Nein, dass ist auch nicht wirklich ne elegante Lösung...
Da das ganze nicht Privat sonder als Unternehmenslösung entwickelt wird, werden mich deren Entwickler wahrscheinlich schlagen wenn sie das sehen :suspekt:

Offensichtlich gibt es wirklich kein Vorbeikommen an den zwei Queries...
Danke trotzdem,
Mike
 
Du musst selbst wissen, was du nicht willst:
Prügel vom Entwickler, weil du pfuscht, oder Prügel vom Sysadmin, weil du seine DB folterst :-)
Das ist schliesslich ein freies Land :suspekt:
 
Zurück