Navigation
 Startseite
 Fachbücher
 Anzeigenmarkt
 Forum
 Webmaster News
 Script Newsletter
 Push Meldungen
 Kontakt
 Script Installation
 Php
 Php Tutorials
 Lernpfade
 Webhoster Vergleich
 Impressum

Community-Bereich
 kostenlos Registrieren
 Anmelden
 Benutzerliste

Script Datenbank
 Script Archiv
 Script Top 20
 Screenshots
 Testberichte

Suche
 

Unsere Php Scripts
 Counter Script
 Umfrage Script
 Bilder Upload Script
 Terminverwaltung
 Simple PHP Forum
 RSS Grabber

Tools und Generatoren
 .htpasswd Generator
 md5 Generator
 base64 Generator
 Markdown to HTML
 Colorpicker
 Unix timestamp Tool
 Unit Test Generator
 TLD Liste
 Webkatalog‑Verzeichnis

Hosterplus.de
Bekommen Sie Speicherplatz (Webspace), Domains...
https://www.Hosterplus.de
Artfiles.de
Bietet Serviceorientierte...
https://www.Artfiles.de
 
 
 

PHP mysqli_data_seek() Function: mysqli_result neu lesen

Sie befinden sich: Home > Php Tutorial > PHP mysqli_data_seek() Func...

PHP mysqli_data_seek() Function: mysqli_result neu lesen
Veröffentlicht am:
07.10.2026
Hits / Besucher:
11
Sprache:
  Deutsch
Tutorial Art:
eigenes
Eingetragen von:
Merkliste:
 
Beschreibung

Eine Mitgliederliste soll zweimal auf derselben Seite erscheinen, oben als Auswahlfeld und weiter unten als Tabelle. Die Abfrage steht, die erste Schleife liefert brav alle Namen, und die zweite gibt nichts aus. Kein Fehler, keine Warnung, einfach nichts. PHP mysqli_data_seek löst das mit einer einzigen Zeile, sobald klar ist, woran es liegt. Wie die Abfrage selbst entsteht, steht im Tutorial zu Prepared Statements.

Illustration zum Tutorial: PHP mysqli_data_seek() Function: mysqli_result neu lesen

Der Grund steckt in einem Bauteil, das man nie zu Gesicht bekommt und trotzdem ständig bewegt: dem Ergebniszeiger.

Warum die zweite Schleife in PHP leer bleibt

Eine Ergebnismenge ist kein Array mit Index, sondern ein Band mit einem Lesekopf. Jeder Abruf schiebt den Kopf eine Zeile weiter. Die Ergebnismenge selbst entsteht mit mysqli_query() oder, wenn die Abfrage mit mysqli_real_query() abgesetzt wurde, mit mysqli_store_result(); beide holen alle Zeilen auf einen Schlag zu PHP herüber, und nur deshalb gibt es überhaupt etwas, worin ein Zeiger springen kann. Ist die letzte Zeile gelesen, bleibt er dort stehen und jeder weitere Abruf liefert null. Genau das beendet die erste while-Schleife, und genau deshalb hat die zweite nichts mehr zu tun. mysqli_data_seek() bewegt diesen Lesekopf wieder.

flowchart TD
    A[Abfrage senden] --> B[Zeiger auf Zeile 0]
    B --> C{Noch eine Zeile?}
    C -- ja --> D[Zeile lesen]
    D --> C
    C -- nein --> E[null, Zeiger am Ende]
    E --> F[data_seek 0]
    F --> B
<?php

$sql = 'SELECT id, name FROM mitglieder'
. ' ORDER BY id';
$r = $db->query($sql);

while ($z = $r->fetch_assoc()) {
echo '<option>' . $z['name'] . '</option>';
}

/* zweiter Durchlauf, ohne Rücksprung */
while ($z = $r->fetch_assoc()) {
echo '<tr><td>' . $z['name'] . '</td></tr>';
}
/* gibt nichts aus */

Die Reparatur ist eine Zeile. PHP mysqli_data_seek() setzt den Lesekopf zurück auf den Anfang, und der zweite Durchlauf liefert dieselben Zeilen noch einmal. Ohne eine weitere Abfrage an den Server, denn die Daten liegen längst in PHP.

<?php

mysqli_data_seek($r, 0);

while ($z = $r->fetch_assoc()) {
echo '<tr><td>' . $z['name'] . '</td></tr>';
}
/* liefert wieder alle Zeilen */

Syntax, Parameter und Rückgabewert im Original

<?php

mysqli_data_seek(
mysqli_result $result,
int $offset
): bool

/* result: a buffered result set
offset: the row number to move to,
counting from zero, must be 0 or more
and below mysqli_num_rows()
returns true on success, false when the
row does not exist
object style: $r->data_seek($offset)
for prepared statements, no return value:
mysqli_stmt_data_seek($stmt, $offset) */

Das Wort buffered ist eine Bedingung und keine Beschreibung: Bei einer ungepufferten Abfrage gibt es nichts, worin man springen könnte. Und der Rückgabetyp bool ist keine Formsache, denn PHP mysqli_data_seek() liefert in zwei ganz verschiedenen Lagen false, die man auseinanderhalten muss.

PHP mysqli_data_seek(): an jede beliebige Zeile springen

Die Zählung beginnt bei null. Das ist der häufigste Abstandsfehler bei diesem Thema und deshalb einen eigenen Absatz wert. An einem nach id sortierten Ergebnis mit zehn Zeilen gemessen: Nach einem Sprung auf Versatz 5 liefert der nächste Abruf die Zeile mit der id 6. Wer die letzte Zeile will, übergibt den Wert aus mysqli_num_rows() minus eins.

<?php

$r = $db->query($sql); /* 10 Zeilen, ORDER BY id */

mysqli_data_seek($r, 5);
$z = $r->fetch_assoc();
echo $z['id']; /* 6, nicht 5 */

mysqli_data_seek($r, mysqli_num_rows($r) - 1);
$letzte = $r->fetch_assoc();

Warum der Sprung ein MySQL ORDER BY braucht

Das ORDER BY in der Abfrage ist kein Schmuck. Ohne feste Reihenfolge ist ein Sprung auf Versatz 5 nicht reproduzierbar, weil der Server die Zeilen in beliebiger Ordnung liefern darf. Wer im Ergebnis springt, sortiert also in SQL.

Praxis 1: erste Zeile als Aufmacher, danach die Liste

Der Klassiker auf Nachrichtenseiten. Der jüngste Beitrag steht groß oben, darunter folgen alle Beiträge als Liste, den ersten eingeschlossen. Ohne PHP mysqli_data_seek() bräuchte das entweder zwei Abfragen oder ein Zwischenarray.

<?php

$r = $db->query($sql);

$erste = $r->fetch_assoc();
echo '<h2>' . $erste['name'] . '</h2>';

mysqli_data_seek($r, 0);
while ($z = $r->fetch_assoc()) {
echo '<li>' . $z['name'] . '</li>';
}

Praxis 2: Summe berechnen, bevor die Posten erscheinen

Eine Rechnung soll den Gesamtbetrag in der Kopfzeile zeigen, die Einzelposten aber darunter. Die Summe steht erst fest, wenn alle Zeilen gelesen sind, und dann ist der Zeiger am Ende. PHP mysqli_data_seek() macht aus dem Henne-Ei-Problem zwei Durchläufe über dieselben Daten.

<?php

$summe = 0.0;
while ($z = $r->fetch_assoc()) {
$summe += (float) $z['betrag'];
}
echo 'Gesamt: ' . number_format($summe, 2);

mysqli_data_seek($r, 0);
while ($z = $r->fetch_assoc()) {
echo $z['name'] . ': ' . $z['betrag'];
}

Praxis 3: innerhalb einer Ergebnismenge blättern

Liegt die Liste ohnehin schon vollständig in PHP, lässt sich die Seitenanzeige daraus bedienen, ohne für jede Seite ein neues LIMIT an den Server zu schicken. Das lohnt sich bei überschaubaren Mengen, etwa einer Auswertung, die sowieso am Stück geholt wird. Ein ähnlicher Umgang mit mysqli_num_rows() findet sich im Tutorial zum CSV-Export aus MySQL.

<?php

$proSeite = 20;
$gesamt = mysqli_num_rows($r);
$seiten = (int) ceil($gesamt / $proSeite);
$versatz = ($seite - 1) * $proSeite;

if ($versatz < $gesamt) {
mysqli_data_seek($r, $versatz);
for ($i = 0; $i < $proSeite; $i++) {
$z = $r->fetch_assoc();
if ($z === null) {
break;
}
echo $z['name'];
}
}

Die Prüfung auf $versatz < $gesamt ist Absicht. Ohne sie läuft PHP mysqli_data_seek() bei einer zu hohen Seitenzahl in den ersten Randfall, und der verhält sich anders, als die meisten erwarten.

Die vier Randfälle

Alle vier sind an PHP 8.4 gegen MySQL 8.0.46 nachgemessen. Der erste enthält eine Überraschung, die in vielen Beiträgen falsch steht.

<?php

/* 1: hinter das Ende, 10 Zeilen im Ergebnis */
var_dump(mysqli_data_seek($r, 10)); /* false */
/* der Zeiger bleibt stehen, wo er war */

/* 2: leeres Ergebnis */
$leer = $db->query('SELECT id FROM x WHERE 0');
var_dump(mysqli_data_seek($leer, 0)); /* false */

/* 3: negativer Versatz */
try {
mysqli_data_seek($r, -1);
} catch (ValueError $e) {
echo $e->getMessage();
/* Argument #2 ($offset) must be greater
than or equal to 0 */
}

/* 4: nach fetch_all steht der Zeiger am Ende */
$alle = $r->fetch_all(MYSQLI_ASSOC);
var_dump($r->fetch_assoc()); /* NULL */
mysqli_data_seek($r, 0); /* wieder lesbar */
Lage Ergebnis Was danach gilt
Versatz hinter der letzten Zeile false Der Zeiger wird nicht bewegt. Der nächste Abruf liefert die Zeile, auf der er ohnehin stand, und nicht null.
Leere Ergebnismenge, Versatz 0 false Kein Fehler, sondern der Normalfall einer Suche ohne Treffer. Vorher mysqli_num_rows() abfragen.
Negativer Versatz ValueError Wird geworfen, nicht zurückgegeben. Eine Prüfung auf false kommt gar nicht erst zum Zug.
Sprung nach fetch_all() true Das Auslesen hat den Zeiger ans Ende geschoben. Nach dem Sprung ist die Menge wieder von vorn lesbar.

Ein misslungener Sprung ändert nichts, er meldet nur, dass er nicht ging. Wer nach einem false weiterliest, bekommt Daten von einer Stelle, die er nicht angesteuert hat. Die zweite Zeile ist die gemeinste Falle des Themas: Eine Fehlerbehandlung der Form if (!mysqli_data_seek($r, 0)) { fehler(); } schlägt bei jeder leeren Suche an, obwohl alles in Ordnung ist.

Eine Beobachtung, die viele Beiträge falsch wiedergeben, gehört noch dazu. Zwei foreach-Schleifen hintereinander über dasselbe gepufferte Ergebnis liefern beide alle Zeilen, ganz ohne Zeigersprung. Der Iterator des Ergebnisobjekts setzt den Zeiger beim Betreten selbst zurück. Danach steht er allerdings am Ende, sodass ein anschließendes while wieder leer bleibt. PHP mysqli_data_seek() braucht man also nicht zwischen zwei foreach, sehr wohl aber beim Wechsel der Schleifenform.

<?php

$r = $db->query($sql);

foreach ($r as $z) { echo $z['id']; } /* 1 bis 10 */
foreach ($r as $z) { echo $z['id']; } /* 1 bis 10 */

while ($z = $r->fetch_assoc()) {
echo $z['id'];
}
/* keine Ausgabe, der Zeiger steht am Ende */

Warum es bei ungepufferten Abfragen nicht geht

Bei MYSQLI_USE_RESULT bleiben die Zeilen auf dem Server, PHP hält immer nur eine im Speicher. Es gibt also nichts, worin ein Zeiger springen könnte. PHP mysqli_data_seek() liefert dort auch kein false, sondern wirft einen harten Error. Ältere Beiträge behaupten das Gegenteil.

<?php

$u = $db->query($sql, MYSQLI_USE_RESULT);

try {
mysqli_data_seek($u, 0);
} catch (Error $e) {
echo $e->getMessage();
/* mysqli_data_seek() cannot be used in
MYSQLI_USE_RESULT mode */
}

Ein gemessener Unterschied lohnt die Erwähnung, weil die Meldungen gleich aussehen. mysqli_num_rows() wirft im selben Modus ebenfalls, liefert aber nach dem vollständigen Durchlauf plötzlich die richtige Zahl. Der Sprung wirft auch dann noch. Die beiden verhalten sich also nicht gleich.

Was der ungepufferte Betrieb dafür an Speicher spart und wie man ihn sauber wieder abräumt, zeigt das Tutorial zu mysqli_free_result() mit gemessenen Zahlen.

Springen oder alles in ein Array holen?

Die Alternative zum Zeigersprung ist ein einmaliges mysqli_fetch_all() und danach beliebig viele foreach über das Array. Beide Wege wurden an 2000 Zeilen mit 200 Durchläufen gemessen, ohne geladenes Xdebug, weil dessen Aufzeichnung solche Werte um ein Vielfaches verzerrt. Der Rücksprung vor fetch_all() gehört dazu: Nach der ersten Messung steht der Zeiger am Ende, und ohne ihn bleibt das Array leer.

<?php

$summe = 0;
$t = microtime(true);
for ($i = 0; $i < 200; $i++) {
mysqli_data_seek($r, 0);
while ($z = $r->fetch_assoc()) {
$summe += (int) $z['id'];
}
}
printf("%.1f ms\n", (microtime(true) - $t) * 1000);
/* 33,1 ms bei 2000 Zeilen */

mysqli_data_seek($r, 0);
$alle = $r->fetch_all(MYSQLI_ASSOC);
$t = microtime(true);
for ($i = 0; $i < 200; $i++) {
foreach ($alle as $z) {
$summe += (int) $z['id'];
}
}
printf("%.1f ms\n", (microtime(true) - $t) * 1000);
/* 7,3 ms, dafür 0,905 MiB im Array */
Kriterium Sprung im Ergebnis Array aus fetch_all()
200 Durchläufe, 2000 Zeilen 33,1 ms 7,3 ms
Speicher 0,094 MiB für die Ergebnismenge 0,905 MiB obendrauf, etwa das Zehnfache
Wahlfreier Zugriff Sprung und Abruf, zwei Schritte direkt über den Index
Passt, wenn zwei oder drei Durchläufe und der Speicher knapp ist oft durchlaufen oder nach Index zugegriffen wird

Das Array ist also etwa viermal schneller und kostet ungefähr das Zehnfache an Speicher. Bei zwei Durchläufen geht es um Millisekunden, und dann gewinnt der Sprung. Wer dagegen in einer Schleife immer wieder auf einzelne Zeilen zugreift, holt einmal ins Array. Noch ein Hinweis zur Abgrenzung: PHP mysqli_data_seek() bewegt den Zeilenzeiger, mysqli_field_seek() den Feldzeiger, und die beiden sind voneinander unabhängig. Gemessen lieferte mysqli_fetch_field() nach einem Sprung auf Zeile 1 weiterhin das erste Feld. Was in diesen Feldobjekten steckt, steht im Tutorial zu mysqli_fetch_field().

Vorbereitete Anweisungen: zwei Wege, eine Vorbedingung

Über mysqli_stmt_get_result() entsteht ein gewöhnliches Ergebnisobjekt, auf dem alles Bisherige unverändert gilt. Der zweite Weg über gebundene Variablen ist der unangenehmere. Dort heißt die Funktion mysqli_stmt_data_seek(), sie gibt nichts zurück, und sie wirkt ausschließlich nach einem mysqli_stmt_store_result(). Fehlt dieser Aufruf, passiert schlicht gar nichts: keine Warnung, kein Fehler, der nächste Abruf liest einfach an der nächsten Zeile weiter.

<?php

/* Weg 1: get_result liefert ein mysqli_result */
$stmt->execute();
$res = $stmt->get_result();
mysqli_data_seek($res, 3); /* true, dann id 4 */

/* Weg 2: gebundene Variablen */
$stmt->execute();
$stmt->store_result(); /* Pflicht, sonst nichts */
$stmt->bind_result($id, $name);
mysqli_stmt_data_seek($stmt, 5);
$stmt->fetch();
echo $id; /* 6 */

Fazit

Der Ergebniszeiger ist ein Lesekopf, kein Index, und er bleibt nach dem letzten Abruf am Ende stehen. Das erklärt die leere zweite Schleife, und PHP mysqli_data_seek() räumt sie mit einer Zeile aus dem Weg. Die Zählung beginnt bei null, das Ergebnis gehört sortiert, und vier Randfälle sollte man kennen, bevor sie einen treffen: ein misslungener Sprung bewegt gar nichts, ein leeres Ergebnis liefert ebenfalls false, ein negativer Versatz wirft, und im ungepufferten Betrieb geht der Sprung gar nicht. Bei vielen Durchläufen ist das Array schneller, bei zwei oder drei gewinnt PHP mysqli_data_seek() ohne zusätzlichen Speicher.

 

Tags:

 

 

Kommentare (0)

Noch keine Kommentare. Sei der Erste!

Melde dich an, um einen Kommentar zu schreiben.
Bücherregal mit drei Büchern: 'PHP 4 - Grundlagen und Profiwissen' von Hanser Verlag, 'Webdesign in a Nutshell' von O'Reilly Verlag, und 'Webgestaltung' von Galileo Computing.