PDO :: fetchAll vs. PDO :: fetch in einer Schleife

72

Nur eine kurze Frage.

Gibt es einen Leistungsunterschied zwischen der Verwendung von PDO :: fetchAll () und PDO :: fetch () in einer Schleife (für große Ergebnismengen)?

Ich rufe in Objekte einer benutzerdefinierten Klasse, wenn das einen Unterschied macht.

Meine anfängliche ungebildete Annahme war, dass fetchAll möglicherweise schneller ist, da PDO mehrere Operationen in einer Anweisung ausführen kann, während mysql_query nur eine ausführen kann. Ich habe jedoch wenig Wissen über das Innenleben von PDO und die Dokumentation sagt nichts darüber aus und ob fetchAll () einfach eine PHP-seitige Schleife ist, die in ein Array geschrieben wird.

Irgendeine Hilfe?

Lotus Notes
quelle
Ich weiß es nicht, aber ich vermute, dass ein Benchmarking trivial wäre.
Timothy

Antworten:

78

Kleiner Benchmark mit 200.000 zufälligen Datensätzen. Wie erwartet ist die fetchAll-Methode schneller, benötigt jedoch mehr Speicher.

Result :
fetchAll : 0.35965991020203s, 100249408b
fetch : 0.39197015762329s, 440b

Der verwendete Benchmark-Code:

<?php
// First benchmark : speed
$dbh = new PDO('mysql:dbname=testage;dbhost=localhost', 'root', '');
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$sql = 'SELECT * FROM test_table WHERE 1';
$stmt = $dbh->query($sql);
$data = array();
$start_all = microtime(true);
$data = $stmt->fetchAll();
$end_all = microtime(true);

$stmt = $dbh->query($sql);
$data = array();
$start_one = microtime(true);
while($data = $stmt->fetch()){}
$end_one = microtime(true);

// Second benchmark : memory usage
$stmt = $dbh->query($sql);
$data = array();
$memory_start_all = memory_get_usage();
$data = $stmt->fetchAll();
$memory_end_all = memory_get_usage();

$stmt = $dbh->query($sql);
$data = array();
$memory_end_one = 0;
$memory_start_one = memory_get_usage();
while($data = $stmt->fetch()){
  $memory_end_one = max($memory_end_one, memory_get_usage());
}

echo 'Result : <br/>
fetchAll : ' . ($end_all - $start_all) . 's, ' . ($memory_end_all - $memory_start_all) . 'b<br/>
fetch : ' . ($end_one - $start_one) . 's, ' . ($memory_end_one - $memory_start_one) . 'b<br/>';
Arkh
quelle
62
Ja, das tust du nicht. Es ist das Ziel des Benchmarks: Zuerst machen Sie einen FetchAll DANN erledigen Sie die Arbeit an den Daten. Zweitens würden Sie eine Zeile abrufen, die Arbeit an dieser Zeile ausführen und dann die nächste Zeile abrufen. Ein gutes Beispiel wäre, wenn Sie eine Datentabelle anzeigen. Müssen Sie ALLE Ihre Daten speichern, bevor Sie in den Puffer schreiben, oder nicht?
Arkh
1
Entschuldigung für das Nekroing, ich verstehe nicht, warum die Leute sagen würden, dass dies ein schlechter Benchmark ist. Es gibt keinen Grund, den gesamten Datensatz zu speichern, es sei denn, Sie geben diese Daten an einen Benutzer zurück. Dies ist in erster Linie einfach schlecht. Verwenden Sie in diesem Fall Paging. Wenn Sie Daten in einer Datenbank massenhaft ändern müssen, sollten Sie dies innerhalb der Datenbank entweder mit einem Skript oder einer gespeicherten Prozedur, z. B. temporären Tabellen, tun.
Populus
3
-1. Dies ist definitiv ein schlechter Maßstab. Der Speicher ist nicht der reale Speicher. memory_get_usage(/* true */)zeigt Ihnen den von PHP selbst zugewiesenen Speicher. Es ist nicht zeigen Ihnen die BIBLIOTHEK zugewiesenen Speicher. Libmysqlclient und mysqlnd verwenden ihren eigenen Speicher. Nicht der Speicher von PHP.
Bwoebi
hängt das nicht davon ab, ob Sie gepufferte Abfragen verwenden oder nicht @bwoebi? Da gepuffert die Standardeinstellung ist, dachte ich, dass Abfrageergebnisse an den "Prozess" von PHP gesendet werden und daher dessen Speicher verwenden? Was den Speicher betrifft, sollte der Benchmark möglicherweise separate Skripte und memory_get_peak_usage (true) verwenden
Félix Gagnon-Grenier
2
@ FélixGagnon-Grenier zeigt memory_get*usage()nur den von PHP gesteuerten Speicher an. Direkte Aufrufe von malloc () oder mmap () werden dabei nicht berücksichtigt. Und sicher, ungepufferte Abfragen lesen grundsätzlich nicht aus dem Socket. Dies bedeutet, dass die Ergebnisse dann auf der MySQL-Serverseite gepuffert werden. Aber gepufferte Abfragen werden clientseitig gespeichert… im Speicher von libmysqlclient, der über malloc () zugewiesen wird. (mysqlnd verwendet emalloc (), das Speicher über den Zend-Speicherzuweiser zuweist)… Aber dieser Benchmark wurde offensichtlich mit libmysqlclient durchgeführt. (Da Zahlen für mysqlnd zu unrealistisch sind.)
bwoebi
12

Eine Sache über PHP, die ich fast immer als wahr befunden habe, ist, dass eine Funktion, die Sie selbst implementieren, fast immer langsamer ist als das PHP-Äquivalent. Dies liegt daran, dass bei der Implementierung von etwas in PHP nicht alle Optimierungen für die Kompilierungszeit von C (in die PHP geschrieben ist) vorhanden sind und der Aufwand für PHP-Funktionsaufrufe hoch ist.

Kendall Hopkins
quelle
Es gibt Zeiten, in denen es sich lohnt, das eingebaute PHP nicht zu verwenden. Zum Beispiel das Durchsuchen eines sortierten Arrays (binäre Suche ftw).
Reece45
3
Ich bin mir nicht sicher, ob ich Ihre Antwort richtig verstehe, aber ich muss nach dem Abrufen erneut einige Operationen an allen Objekten ausführen, die zweifellos eine weitere foreach-Schleife erfordern würden. Sollte ich mich nur daran halten, jeweils ein Objekt abzurufen und die Operationen für jedes Objekt auszuführen, während es abgerufen wird?
Lotus Notes
@ AlReece45 Sie haben zwei völlig unterschiedliche Funktionen beschrieben. Ich sprach über die Neuimplementierung der Sortierfunktion in PHP im Vergleich zur Verwendung von PHPs sort. @Byron Ich wette, Sie werden feststellen, dass das Abrufen aller Ergebnisse mit fetchAll () immer noch schneller ist, aber Sie können es mit einem Benchmark vergleichen, microtime(TRUE)wenn Sie Zweifel haben.
Kendall Hopkins
@ Reece45 Binäre Suche ist logarithmisch; Es sollte schnell gehen, ob es in C oder PHP geschrieben ist. OTOH zu sortieren ist O(n log n)- es gibt ein bisschen mehr Einsparungen zu erzielen .
Mpen
Auf theoretischer Ebene mag es sinnvoll sein, die binäre Suche in PHP zu implementieren. Wenn Ihr N jedoch nicht sehr groß ist (was meistens nicht zutrifft, es sei denn, Sie machen etwas falsch), ist es besser, nur das O (N) zu verwenden, das PHP bietet.
Kendall Hopkins
10

Alle Benchmarks, über denen der "Speicherbedarf" gemessen wird, sind aus dem sehr einfachen Grund tatsächlich falsch.

PDO lädt standardmäßig alle Dinge in den Speicher und es ist egal, ob Sie fetch oder fetchAll verwenden. Um die Vorteile einer ungepufferten Abfrage wirklich nutzen zu können, sollten Sie PDO anweisen, ungepufferte Abfragen zu verwenden:

$db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);

In diesem Fall sehen Sie einen großen Unterschied im Speicherbedarf des Skripts

iVariable
quelle
Was ist der Unterschied zwischen der Verwendung $stmt->fetch()bei Verwendung gepufferter Abfragen (Standardeinstellung) und der Verwendung $stmt->fetch()bei ungepufferten Abfragen ( PDO::MYSQL_ATTR_USE_BUFFERED_QUERYAttribut festgelegt auf false)? Ich habe gesehen, dass selbst wenn Sie den standardmäßigen gepufferten Modus verwenden, dies $stmt->fetch()für sehr große Datenmengen funktioniert und $stmt->fetchAll()möglicherweise einen Speicherbeschränkungsfehler zurückgibt. Also ist $stmt->fetch() irgendwie unbuffered?
Tonix
9

@Arkh

// $data in this case is an array of rows;

$data = $stmt->fetchAll();


// $data in this case is just one row after each loop;

while($data = $stmt->fetch()){}


// Try using

$i = 0;

while($data[$i++] = $stmt->fetch()){}

Der Speicherunterschied sollte vernachlässigbar werden

Mihai Stancu
quelle
2
@stancu Die oberen und unteren Varianten sind praktisch identisch, und das zusätzliche MEM, das mit fetch () angezeigt wird, ist wahrscheinlich ein Artefakt des Overheads von while (). Der Punkt von fetch () besteht darin, jeweils eine Zeile zu verarbeiten und while () zu verwenden, um dasselbe zu erreichen, wie fetchAll (PDO :: FETCH_NUM) albern ist, da Sie die im PDO stattfindenden Compiler-Optimierungen auf C-Ebene verlieren Modul.
DavidScherer
4

Wie Mihai Stancu sagte, gibt es fast keinen Speicherunterschied, obwohl fetchAll fetch + while schlägt.

Result : 
fetchAll : 0.160676956177s, 118539304b
fetch : 0.121752023697s, 118544392b

Ich habe die obigen Ergebnisse mit korrektem Laufen erhalten:

$i = 0;
while($data[$i++] = $stmt->fetch()){
    //
}

Das fetchAll verbraucht also weniger Speicher, aber fetch + while ist schneller! :) :)

Rihards
quelle
6
Schneller? 0,16 ( fetchAll) gegen 0,12 ( fetch)
Joost
1
Bei erheblich größeren Ergebnismengen würden Sie einen signifikanten Unterschied zwischen PDOStatement :: fetch () und PDOStatement :: fetchALL () feststellen. Die Bestimmung, was als "signifikant größer" qualifiziert ist, hängt von der Größe jeder Zeile ab. Darüber hinaus verwendet PDOStatement :: Fetch () / fetchAll () standardmäßig den Abrufmodus PDO :: FETCH_BOTH, der die Größe jeder Zeile effektiv verdoppelt. Wenn Sie dies ändern, kann dies dazu beitragen, die MEM-Nutzung bei großen Ergebnismengen zu verringern.
DavidScherer
Bei der Abwärtsabstimmung wurde auch keine Referenzstatistik für Ihren Benchmark bereitgestellt, was ihn von Natur aus fehlerhaft machte. Die verschiedenen Gemeinkosten mit PHP-Funktionen und was nicht würde den MEM-Unterschied erklären.
DavidScherer
4

Aber wenn Sie die abgerufenen Daten in einem Array speichern, ist die Speichernutzung sicher gleich?

<?php
define('DB_HOST', 'localhost');
define('DB_USER', 'root');
define('DB_PASS', '');
// database to use
define('DB', 'test');
try
{
   $dbh = new \PDO('mysql:dbname='. DB .';host='. DB_HOST, DB_USER, DB_PASS);   $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
   $sql = 'SELECT * FROM users WHERE 1';
   $stmt = $dbh->query($sql);
   $data = array();
   $start_all = microtime(true);
   $data = $stmt->fetchAll();
   $end_all = microtime(true);

   $stmt = $dbh->query($sql);
   $data = array();
   $start_one = microtime(true);
   while($data = $stmt->fetch()){}
   $end_one = microtime(true);

   // Second benchmark : memory usage
   $stmt = $dbh->query($sql);
   $data = array();
   $memory_start_all = memory_get_usage();
   $data = $stmt->fetchAll();
   $memory_end_all = memory_get_usage();

   $stmt = $dbh->query($sql);
   $data = array();
   $memory_end_one = 0;
   $memory_start_one = memory_get_usage();
   while($data[] = $stmt->fetch()){
     $memory_end_one = max($memory_end_one, memory_get_usage());
   }

   echo 'Result : <br/>
   fetchAll : ' . ($end_all - $start_all) . 's, ' . ($memory_end_all - $memory_start_all) . 'b<br/>
   fetch : ' . ($end_one - $start_one) . 's, ' . ($memory_end_one - $memory_start_one) . 'b<br/>';
}
catch ( PDOException $e )
{
   echo $e->getMessage();
}
?>

Result : 
fetchAll : 2.6941299438477E-5s, 9824b
fetch : 1.5974044799805E-5s, 9824b
Andy
quelle
2

Ich weiß, dass dies ein altes Thema ist, aber ich stoße darauf mit der gleichen Frage. Nachdem ich meinen eigenen einfachen "Benchmark" durchgeführt und gelesen hatte, was andere hier geschrieben haben, kam ich zu dem Schluss, dass dies keine exakte Wissenschaft ist, und obwohl man sich bemühen sollte, den hochwertigen, leichten Code zu schreiben, macht es keinen Sinn, zu Beginn zu viel Zeit zu verschwenden von dem Projekt.

Mein Vorschlag ist: Sammeln Sie Daten, indem Sie den Code (in der Beta?) Eine Weile ausführen und dann mit der Optimierung beginnen.

In meinem einfachen Benchmark (nur getestete Ausführungszeit) habe ich Ergebnisse, die BEIDE zwischen 5% und 50% variieren. Ich führe beide Optionen im selben Skript aus, aber wenn ich fetch + ausführe, war es zuerst schneller als fetchall und umgekehrt. (Ich weiß, ich hätte sie einzeln ausführen und ein paar hundert Mal den Median und den Mittelwert ermitteln und dann vergleichen sollen, aber - wie ich zu Beginn gesagt habe - bin ich zu dem Schluss gekommen, dass es in meinem Fall zu früh ist, damit zu beginnen.)

StrayObject
quelle