1
0
mirror of https://github.com/php/doc-de.git synced 2026-03-23 23:02:13 +01:00
Files
archived-doc-de/reference/mysqli/quickstart.xml
Martin Samesch 6b5305138b Sync with EN
2023-04-17 20:04:33 +02:00

1701 lines
54 KiB
XML

<?xml version="1.0" encoding="utf-8"?>
<!-- $Revision$ -->
<!-- EN-Revision: 60a292997b3c6341cf52099d901aa0b5f8673d87 Maintainer: samesch Status: ready -->
<chapter xml:id="mysqli.quickstart" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Kurzanleitung für den schnellen Einstieg</title>
<para>
Diese Kurzanleitung hilft dabei, sich mit der PHP-MySQL-API vertraut zu
machen.
</para>
<para>
Diese Kurzanleitung gibt einen Überblick über die mysqli-Erweiterung. Es
werden Beispiele für alle wichtigen Aspekte der API beschrieben.
Datenbankkonzepte werden in dem Maße erklärt, wie es für die Darstellung
von MySQL-spezifischen Konzepten erforderlich ist.
</para>
<para>
Voraussetzungen: Vertrautheit mit der Programmiersprache PHP, der Sprache
SQL und Grundkenntnisse über den MySQL-Server.
</para>
<section xml:id="mysqli.quickstart.dual-interface">
<title>Prozedurale und objektorientierte Schnittstelle</title>
<para>
Die mysqli-Erweiterung verfügt über eine doppelte Schnittstelle. Sie
unterstützt das prozedurale und das objektorientierte Programmier-Paradigma.
</para>
<para>
Benutzer, die von der alten mysql-Erweiterung umsteigen, werden vielleicht
die prozedurale Schnittstelle bevorzugen. Die prozedurale Schnittstelle ist
ähnlich wie die der alten mysql Erweiterung. In vielen Fällen unterscheiden
sich die Funktionsnamen nur durch das Präfix. Einige mysqli-Funktionen
benötigen ein Verbindungs-Handle als ersten Parameter, während
entsprechende Funktionen der alten mysql-Schnittstelle es als optionalen
letzten Parameter nehmen.
</para>
<para>
<example>
<title>Einfacher Umstieg von der alten mysql-Erweiterung</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = mysqli_connect("example.com", "user", "password", "database");
$result = mysqli_query($mysqli, "SELECT 'Bitte verwenden Sie nicht die veraltete mysql-Erweiterung für neue Projekte. ' AS _msg FROM DUAL");
$row = mysqli_fetch_assoc($result);
echo $row['_msg'];
$mysql = mysql_connect("example.com", "user", "password");
mysql_select_db("test");
$result = mysql_query("SELECT 'Verwenden Sie stattdessen die mysqli-Erweiterung.' AS _msg FROM DUAL", $mysql);
$row = mysql_fetch_assoc($result);
echo $row['_msg'];
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
Bitte verwenden Sie nicht die veraltete mysql-Erweiterung für neue Projekte. Verwenden Sie stattdessen die mysqli-Erweiterung.
]]>
</screen>
</example>
</para>
<para>
<emphasis role="bold">Die objektorientierte Schnittstelle</emphasis>
</para>
<para>
Neben der klassischen prozeduralen Schnittstelle kann der Benutzer auch die
objektorientierte Schnittstelle verwenden. Die Dokumentation ist anhand der
objektorientierten Schnittstelle gegliedert. Um den Einstieg zu
erleichtern, sind die Funktionen der objektorientierten Schnittstelle nach
ihrem Zweck gruppiert. Der Referenzteil enthält Beispiele für beide
Syntaxvarianten.
</para>
<para>
Es gibt keine signifikanten Leistungsunterschiede zwischen den beiden
Schnittstellen. Der Benutzer kann seine Wahl nach persönlichen Vorlieben
treffen.
</para>
<para>
<example>
<title>Objektorientierte und prozedurale Schnittstelle</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = mysqli_connect("example.com", "user", "password", "database");
$result = mysqli_query($mysqli, "SELECT 'A world full of ' AS _msg FROM DUAL");
$row = mysqli_fetch_assoc($result);
echo $row['_msg'];
$mysqli = new mysqli("example.com", "user", "password", "database");
$result = $mysqli->query("SELECT 'choices to please everybody.' AS _msg FROM DUAL");
$row = $result->fetch_assoc();
echo $row['_msg'];
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
A world full of choices to please everybody.
]]>
</screen>
</example>
</para>
<para>
Für die Kurzanleitung wird die objektorientierte Schnittstelle verwendet,
weil der Referenzabschnitt entsprechend aufgebaut ist.
</para>
<para>
<emphasis role="bold">Stile miteinander kombinieren</emphasis>
</para>
<para>
Es ist jederzeit möglich, zwischen den Stilen zu wechseln. Das Kombinieren
beider Stile wird aus Gründen des Programmierstils und der Verständlichkeit
des Codes nicht empfohlen.
</para>
<para>
<example>
<title>Schlechter Programmierstil</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = new mysqli("example.com", "user", "password", "database");
$result = mysqli_query($mysqli, "SELECT 'Möglich, aber schlechter Stil.' AS _msg FROM DUAL");
if ($row = $result->fetch_assoc()) {
echo $row['_msg'];
}
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
Möglich, aber schlechter Stil.
]]>
</screen>
</example>
</para>
<para>
<emphasis role="bold">Siehe auch</emphasis>
</para>
<para>
<simplelist>
<member><methodname>mysqli::__construct</methodname></member>
<member><methodname>mysqli::query</methodname></member>
<member><methodname>mysqli_result::fetch_assoc</methodname></member>
<member><link linkend="mysqli.connect-errno">$mysqli::connect_errno</link></member>
<member><link linkend="mysqli.connect-error">$mysqli::connect_error</link></member>
<member><link linkend="mysqli.errno">$mysqli::errno</link></member>
<member><link linkend="mysqli.error">$mysqli::error</link></member>
<member><link linkend="mysqli.summary">Übersicht über die Funktionen der MySQLi-Erweiterung</link></member>
</simplelist>
</para>
</section>
<section xml:id="mysqli.quickstart.connections">
<title>Datenbankverbindungen</title>
<para>
Der MySQL-Server unterstützt die Verwendung verschiedener
Transportschichten für Verbindungen. Verbindungen verwenden TCP/IP,
Unix-Domain-Sockets oder Windows Named Pipes.
</para>
<para>
Der Hostname <literal>localhost</literal> hat eine besondere Bedeutung. Er
ist an die Verwendung von Unix-Domain-Sockets gebunden. Um eine
TCP/IP-Verbindung zum Localhost zu öffnen, muss <literal>127.0.0.1</literal>
anstelle des Hostnamens <literal>localhost</literal> verwendet werden.
</para>
<para>
<example>
<title>Die besondere Bedeutung von localhost</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = new mysqli("localhost", "user", "password", "database");
echo $mysqli->host_info . "\n";
$mysqli = new mysqli("127.0.0.1", "user", "password", "database", 3306);
echo $mysqli->host_info . "\n";
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
Localhost via UNIX socket
127.0.0.1 via TCP/IP
]]>
</screen>
</example>
</para>
<para>
<emphasis role="bold">Standardwerte für Verbindungsparameter</emphasis>
</para>
<para>
Je nach verwendeter Verbindungsfunktion können verschiedene Parameter
weggelassen werden. Wenn ein Parameter nicht angegeben wird, versucht die
Erweiterung, die Standardwerte zu verwenden, die in der
PHP-Konfigurationsdatei festgelegt sind.
</para>
<para>
<example>
<title>Standardwerte festlegen</title>
<programlisting role="ini">
<![CDATA[
mysqli.default_host=192.168.2.27
mysqli.default_user=root
mysqli.default_pw=""
mysqli.default_port=3306
mysqli.default_socket=/tmp/mysql.sock
]]>
</programlisting>
</example>
</para>
<para>
Die entsprechenden Parameterwerte werden dann an die Client-Bibliothek
übergeben, die von der Erweiterung verwendet wird. Wenn die
Client-Bibliothek leere oder nicht gesetzte Parameter findet, kann sie die
Standardwerte der Bibliothek verwenden.
</para>
<para>
<emphasis role="bold">Standardwerte der integrierten Verbindungsbibliothek</emphasis>
</para>
<para>
Wenn der Wert von host nicht definiert oder leer ist, verwendet die
Client-Bibliothek standardmäßig eine Unix-Socket-Verbindung zu
<literal>localhost</literal>. Wenn der Socket undefiniert oder leer ist und
eine Unix-Socket-Verbindung angefordert wird, dann wird versucht, eine
Verbindung zum Standardsocket <literal>/tmp/mysql.sock</literal>
aufzubauen.
</para>
<para>
Auf Windows-Systemen wird der Hostname <literal>.</literal> von der
Client-Bibliothek als Versuch interpretiert, eine auf einer Windows Namend
Pipe basierende Verbindung zu öffnen. In diesem Fall wird der
Socket-Parameter als Name der Pipe interpretiert. Wird er nicht angegeben
oder ist er leer, dann wird der Socket (Name der Pipe) standardmäßig auf
<literal>\\.\pipe\MySQL</literal> gesetzt.
</para>
<para>
Wenn eine Verbindung weder basierend auf einen Unix-Domänen-Socket noch auf
eine Windows Named Pipe aufgebaut werden soll und der Parameterwert für den
Port nicht gesetzt ist, verwendet die Bibliothek standardmäßig den Port
<literal>3306</literal>.
</para>
<para>
Die <link linkend="mysqlnd.overview">mysqlnd</link>-Bibliothek und die
MySQL-Client-Bibliothek (libmysqlclient) implementieren dieselbe Logik zur
Bestimmung der Standardwerte.
</para>
<para>
<emphasis role="bold">Optionen für die Verbindung</emphasis>
</para>
<para>
Die Verbindungsoptionen sind verfügbar, um &zb; Initialisierungsbefehle zu
setzen, die beim Aufbau der Verbindung ausgeführt werden, oder um die
Verwendung eines bestimmten Zeichensatzes anzugeben. Die
Verbindungsoptionen müssen gesetzt werden, bevor eine Netzwerkverbindung
aufgebaut wird.
</para>
<para>
Um eine Verbindungsoption zu setzen, muss der Verbindungsvorgang in drei
Schritten durchgeführt werden: erstellen eines Verbindungs-Handles mit
<function>mysqli_init</function> oder
<methodname>mysqli::__construct</methodname>, setzen der gewünschten
Optionen mit <methodname>mysqli::options</methodname> und Aufbau der
Netzwerkverbindung mit <methodname>mysqli::real_connect</methodname>.
</para>
<para>
<emphasis role="bold">Pooling von Verbindungen</emphasis>
</para>
<para>
Die mysqli-Erweiterung unterstützt persistente Datenbankverbindungen, die
eine spezielle Art von gepoolten Verbindungen sind. Standardmäßig wird jede
Datenbankverbindung, die von einem Skript geöffnet wird, entweder explizit
durch den Benutzer während der Laufzeit geschlossen oder am Ende des
Skripts automatisch freigegeben. Eine persistente Verbindung wird nicht
geschlossen. Stattdessen wird sie in einen Pool gelegt, um später
wiederverwendet zu werden, wenn eine Verbindung zum selben Server mit
denselben Benutzernamen, Passwort, Socket, Port und Standarddatenbank
geöffnet wird. Die Wiederverwendung spart Verbindungs-Overhead.
</para>
<para>
Jeder PHP-Prozess verwendet seinen eigenen mysqli-Verbindungspool. Abhängig
vom Einsatzmodell des Webservers kann ein PHP-Prozess eine oder mehrere
Anfragen bedienen. Daher kann eine in einem Pool gehaltene Verbindung von
einem oder mehreren Skripten nacheinander verwendet werden.
</para>
<para>
<emphasis role="bold">Persistente Verbindung</emphasis>
</para>
<para>
Wenn im Verbindungspool für eine bestimmte Kombination von Host,
Benutzername, Passwort, Socket, Port und Standarddatenbank keine unbenutzte
persistente Verbindung gefunden wird, öffnet mysqli eine neue Verbindung.
Die Verwendung persistenter Verbindungen kann mit der PHP-Direktive
<link linkend="ini.mysqli.allow-persistent">mysqli.allow_persistent</link>
aktiviert und deaktiviert werden. Die Gesamtzahl der von einem Skript
geöffneten Verbindungen kann mit
<link linkend="ini.mysqli.max-links">mysqli.max_links</link> begrenzt
werden. Die maximale Anzahl von persistenten Verbindungen pro PHP-Prozess
kann mit
<link linkend="ini.mysqli.max-persistent">mysqli.max_persistent</link>
begrenzt werden. Bitte beachten Sie, dass der Webserver viele PHP-Prozesse
erzeugen kann.
</para>
<para>
Eine häufige Kritik an persistenten Verbindungen ist, dass ihr Zustand vor
der Wiederverwendung nicht zurückgesetzt wird. Zum Beispiel werden offene
und nicht abgeschlossene Transaktionen nicht nicht automatisch
zurückgesetzt. Aber auch Berechtigungsänderungen, die in der Zeit zwischen
der Aufnahme der Verbindung in den Pool und ihrer Wiederverwendung
vorgenommen wurden, werden nicht berücksichtigt. Dies kann als
unerwünschter Nebeneffekt angesehen werden. Andererseits kann der Name
<literal>persistent</literal> als Zusage verstanden werden, dass der
Zustand erhalten bleibt.
</para>
<para>
Die mysqli-Erweiterung unterstützt beide Arten einer persistenten
Verbindung: den persistenten Zustand und den vor der Wiederverwendung
zurückgesetzten Zustand. Die Voreinstellung ist Zurücksetzen. Bevor eine
persistente Verbindung wiederverwendet wird, ruft die mysqli-Erweiterung
implizit <methodname>mysqli::change_user</methodname> auf, um den Zustand
zurückzusetzen. Die persistente Verbindung erscheint dem Benutzer so, als
ob sie gerade geöffnet worden wäre. Es sind keine Spuren von früheren
Verwendungen sichtbar.
</para>
<para>
Der Aufruf von <methodname>mysqli::change_user</methodname> ist eine
aufwändige Operation. Um die beste Leistung zu erzielen, sollten Benutzer
die Erweiterung mit dem Kompilierungsflag
<constant>MYSQLI_NO_CHANGE_USER_ON_PCONNECT</constant> neu kompilieren.
</para>
<para>
Es ist dem Benutzer überlassen, zwischen sicherem Verhalten und bester
Leistung zu wählen. Beides sind berechtigte Optimierungsziele. Um die
Benutzung zu erleichtern, wurde das sichere Verhalten auf Kosten der
maximalen Leistung als Standard festgelegt.
</para>
<para>
<emphasis role="bold">Siehe auch</emphasis>
</para>
<para>
<simplelist>
<member><methodname>mysqli::__construct</methodname></member>
<member><function>mysqli_init</function></member>
<member><methodname>mysqli::options</methodname></member>
<member><methodname>mysqli::real_connect</methodname></member>
<member><methodname>mysqli::change_user</methodname></member>
<member><link linkend="mysqli.get-host-info">$mysqli::host_info</link></member>
<member><link linkend="mysqli.configuration">MySQLi-Konfigurationsoptionen</link></member>
<member><link linkend="features.persistent-connections">Persistente Datenbankverbindungen</link></member>
</simplelist>
</para>
</section>
<section xml:id="mysqli.quickstart.statements">
<title>Ausführen von Anweisungen</title>
<para>
Anweisungen können mit <methodname>mysqli::query</methodname>,
<methodname>mysqli::real_query</methodname> und
<methodname>mysqli::multi_query</methodname> ausgeführt werden. Die
Funktion <methodname>mysqli::query</methodname> wird am häufigsten
verwendet und kombiniert in einem Aufruf die auszuführende Anweisung mit
einem gepufferten Abruf der Ergebnismenge, falls vorhanden. Der Aufruf von
<methodname>mysqli::query</methodname> ist gleichbedeutend mit dem Aufruf
von <methodname>mysqli::real_query</methodname> gefolgt von
<methodname>mysqli::store_result</methodname>.
</para>
<para>
<example>
<title>Ausführen von Anfragen</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT)");
]]>
</programlisting>
</example>
</para>
<para>
<emphasis role="bold">Gepufferte Ergebnisse</emphasis>
</para>
<para>
Nach der Ausführung der Anweisung können die Ergebnisse entweder alle auf
einmal vom Server abgerufen werden oder Zeile für Zeile. Die clientseitige
Pufferung der Ergebnismenge ermöglicht es dem Server, die mit den
Ergebnissen der Anweisung verbundenen Ressourcen so früh wie möglich
freizugeben. Im Allgemeinen verarbeiten Clients die Ergebnismengen langsam.
Es wird daher empfohlen, gepufferte Ergebnismengen zu verwenden.
<methodname>mysqli::query</methodname> kombiniert die Ausführung einer
Anweisung mit der Pufferung der Ergebnismenge.
</para>
<para>
PHP-Anwendungen können frei durch gepufferte Ergebnismengen navigieren. Da
die Ergebnismengen im Speicher des Clients gehalten werden, ist die
Navigation sehr schnell. Bitte bedenken Sie, dass es oft einfacher ist,
einen Client zu skalieren als den Server.
</para>
<para>
<example>
<title>Navigation durch gepufferte Ergebnisse</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT)");
$mysqli->query("INSERT INTO test(id) VALUES (1), (2), (3)");
$result = $mysqli->query("SELECT id FROM test ORDER BY id ASC");
echo "Umgekehrte Reihenfolge...\n";
for ($row_no = $result->num_rows - 1; $row_no >= 0; $row_no--) {
$result->data_seek($row_no);
$row = $result->fetch_assoc();
echo " id = " . $row['id'] . "\n";
}
echo "Reihenfolge der Ergebnisse...\n";
foreach ($result as $row) {
echo " id = " . $row['id'] . "\n";
}
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
Umgekehrte Reihenfolge...
id = 3
id = 2
id = 1
Reihenfolge der Ergebnisse...
id = 1
id = 2
id = 3
]]>
</screen>
</example>
</para>
<para>
<emphasis role="bold">Ungepufferte Ergebnismengen</emphasis>
</para>
<para>
Wenn der Client-Speicher knapp ist und es nicht notwendig ist,
Server-Ressourcen so früh wie möglich freizugeben, um die Serverlast gering
zu halten, können ungepufferte Ergebnisse verwendet werden. Das Blättern
durch ungepufferte Ergebnisse ist erst möglich, nachdem alle Zeilen gelesen
wurden.
</para>
<para>
<example>
<title>Navigation durch ungepufferte Ergebnisse</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli->real_query("SELECT id FROM test ORDER BY id ASC");
$result = $mysqli->use_result();
echo "Reihenfolge der Ergebnisse...\n";
foreach ($result as $row) {
echo " id = " . $row['id'] . "\n";
}
]]>
</programlisting>
</example>
</para>
<para>
<emphasis role="bold">Datentypen der Werte in der Ergebnismenge</emphasis>
</para>
<para>
Die Funktionen <methodname>mysqli::query</methodname>,
<methodname>mysqli::real_query</methodname> und
<methodname>mysqli::multi_query</methodname> werden verwendet, um nicht
vorbereitete Anweisungen auszuführen. Auf der Ebene des
Client-Server-Protokolls von MySQL werden der Befehl
<literal>COM_QUERY</literal> und das Textprotokoll für die Ausführung von
Anweisungen verwendet. Mit dem Textprotokoll wandelt der MySQL-Server alle
Daten einer Ergebnismenge vor dem Senden in Zeichenketten um. Diese
Umwandlung wird unabhängig vom Datentyp der Spalten der SQL-Ergebnismenge
durchgeführt. Die mysql-Client-Bibliotheken empfangen alle Spaltenwerte als
Zeichenketten. Es wird keine weitere clientseitige Umwandlung durchgeführt,
um Spalten zurück in ihre nativen Typen umzuwandeln. Stattdessen werden
alle Werte als PHP-Zeichenketten bereitgestellt.
</para>
<para>
<example>
<title>Das Textprotokoll gibt standardmäßig Zeichenketten zurück</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT, label CHAR(1))");
$mysqli->query("INSERT INTO test(id, label) VALUES (1, 'a')");
$result = $mysqli->query("SELECT id, label FROM test WHERE id = 1");
$row = $result->fetch_assoc();
printf("id = %s (%s)\n", $row['id'], gettype($row['id']));
printf("label = %s (%s)\n", $row['label'], gettype($row['label']));
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
id = 1 (string)
label = a (string)
]]>
</screen>
</example>
</para>
<para>
Wenn die mysqlnd-Bibliothek verwendet wird, können Integer- und
Float-Spalten durch Setzen der Verbindungsoption
<constant>MYSQLI_OPT_INT_AND_FLOAT_NATIVE</constant> in PHP-Zahlen
umgewandelt werden. Wenn sie gesetzt ist, überprüft die mysqlnd-Bibliothek
die Metadaten der Spaltentypen in der Ergebnismenge und wandelt
numerische SQL-Spalten in PHP-Zahlen um, wenn der Wertebereich des
PHP-Datentyps dies zulässt. Auf diese Weise werden &zb; SQL-INT-Spalten
als Integer zurückgegeben.
</para>
<para>
<example>
<title>Native Datentypen bei mysqlnd und Verbindungsoption</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli();
$mysqli->options(MYSQLI_OPT_INT_AND_FLOAT_NATIVE, 1);
$mysqli->real_connect("example.com", "user", "password", "database");
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT, label CHAR(1))");
$mysqli->query("INSERT INTO test(id, label) VALUES (1, 'a')");
$result = $mysqli->query("SELECT id, label FROM test WHERE id = 1");
$row = $result->fetch_assoc();
printf("id = %s (%s)\n", $row['id'], gettype($row['id']));
printf("label = %s (%s)\n", $row['label'], gettype($row['label']));
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
id = 1 (integer)
label = a (string)
]]>
</screen>
</example>
</para>
<para>
<emphasis role="bold">Siehe auch</emphasis>
</para>
<para>
<simplelist>
<member><methodname>mysqli::__construct</methodname></member>
<member><methodname>mysqli::options</methodname></member>
<member><methodname>mysqli::real_connect</methodname></member>
<member><methodname>mysqli::query</methodname></member>
<member><methodname>mysqli::multi_query</methodname></member>
<member><methodname>mysqli::use_result</methodname></member>
<member><methodname>mysqli::store_result</methodname></member>
</simplelist>
</para>
</section>
<section xml:id="mysqli.quickstart.prepared-statements">
<title>Vorbereitete Anweisungen (Prepared Statements)</title>
<para>
Die MySQL-Datenbank unterstützt vorbereitete Anweisungen. Vorbereitete
Anweisungen oder parametrisierte Anweisungen ermöglichen die wiederholte
und effiziente Ausführung derselben Anweisung und schützen gleichzeitig vor
SQL-Injections.
</para>
<para>
<emphasis role="bold">Grundlegender Ablauf</emphasis>
</para>
<para>
Die Ausführung einer vorbereiteten Anweisung besteht aus zwei Phasen: der
Vorbereitung und der Ausführung. In der Vorbereitungsphase wird eine
Anweisungsvorlage an den Datenbankserver gesendet. Der Server führt eine
Syntaxprüfung durch und initialisiert Server-interne Ressourcen für die
spätere Verwendung.
</para>
<para>
Der MySQL-Server unterstützt die Verwendung des anonymen,
positionsbezogenen Platzhalters <literal>?</literal>.
</para>
<para>
Auf das Vorbereiten folgt das Ausführen. Während der Ausführung bindet der
Client die Parameterwerte und sendet sie an den Server. Der Server führt
die Anweisung mit den gebundenen Werten unter Verwendung der zuvor
erstellten internen Ressourcen aus.
</para>
<para>
<example>
<title>Vorbereitete Anweisung</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
/* Nicht-vorbereitete Anweisung */
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT, label TEXT)");
/* Vorbereitete Anweisung, Stufe 1: vorbereiten */
$stmt = $mysqli->prepare("INSERT INTO test(id, label) VALUES (?, ?)");
/* Vorbereitete Anweisung, Stufe 2: binden und ausführen */
$id = 1;
$label = 'PHP';
$stmt->bind_param("is", $id, $label); // "is" bedeutet, dass $id als Integer und
// $label als Zeichenkette gebunden ist
$stmt->execute();
]]>
</programlisting>
</example>
</para>
<para>
<emphasis role="bold">Mehrmalige Ausführung</emphasis>
</para>
<para>
Eine vorbereitete Anweisung kann mehrmals ausgeführt werden. Bei jeder
Ausführung wird der aktuelle Wert der gebundenen Variablen ausgewertet und
an den Server gesendet. Die Anweisung wird nicht erneut analysiert und die
Anweisungsvorlage wird nicht erneut an den Server übertragen.
</para>
<para>
<example>
<title>INSERT einmal vorbereitet, mehrfach ausgeführt</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
/* Nicht-vorbereitete Anweisung */
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT, label TEXT)");
/* Vorbereitete Anweisung, Stufe 1: vorbereiten */
$stmt = $mysqli->prepare("INSERT INTO test(id, label) VALUES (?, ?)");
/* Vorbereitete Anweisung, Stufe 2: binden und ausführen */
$stmt->bind_param("is", $id, $label); // "is" bedeutet, dass $id als Integer und
// $label als Zeichenkette gebunden ist
$data = [
1 => 'PHP',
2 => 'Java',
3 => 'C++'
];
foreach ($data as $id => $label) {
$stmt->execute();
}
$result = $mysqli->query('SELECT id, label FROM test');
var_dump($result->fetch_all(MYSQLI_ASSOC));
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
array(3) {
[0]=>
array(2) {
["id"]=>
string(1) "1"
["label"]=>
string(3) "PHP"
}
[1]=>
array(2) {
["id"]=>
string(1) "2"
["label"]=>
string(4) "Java"
}
[2]=>
array(2) {
["id"]=>
string(1) "3"
["label"]=>
string(3) "C++"
}
}
]]>
</screen>
</example>
</para>
<para>
Jede vorbereitete Anweisung beansprucht Ressourcen auf dem Server, weshalb
sie sofort nach ihrer Verwendung explizit geschlossen werden sollte. Falls
dies nicht explizit geschieht, wird die Anweisung geschlossen, wenn das
Anweisungs-Handle von PHP freigegeben wird.
</para>
<para>
Die Verwendung einer vorbereiteten Anweisung ist nicht immer die
effizienteste Art, eine Anweisung auszuführen. Eine vorbereitete Anweisung,
die nur einmal ausgeführt wird, verursacht mehr Client-Server-Umläufe
(Roundtrips) als eine nicht-vorbereitete Anweisung. Aus diesem Grund wird
die <literal>SELECT</literal>-Anweisung nicht als vorbereitete Anweisung
ausgeführt.
</para>
<para>
Außerdem sollte für INSERTs die Verwendung der multi-INSERT-Syntax von
MySQL in Betracht gezogen werden. Für das Beispiel erfordert multi-INSERT
weniger Umläufe zwischen Server und Client als die oben gezeigte
vorbereitete Anweisung.
</para>
<para>
<example>
<title>Weniger Umläufe durch multi-INSERT-SQL</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT)");
$values = [1, 2, 3, 4];
$stmt = $mysqli->prepare("INSERT INTO test(id) VALUES (?), (?), (?), (?)");
$stmt->bind_param('iiii', ...$values);
$stmt->execute();
]]>
</programlisting>
</example>
</para>
<para>
<emphasis role="bold">Datentypen der Werte in der Ergebnismenge</emphasis>
</para>
<para>
Das MySQL-Client-Server-Protokoll definiert unterschiedliche
Datenübertragungsprotokolle für vorbereitete Anweisungen und
nicht-vorbereitete Anweisungen. Vorbereitete Anweisungen verwenden das
sogenannte Binärprotokoll. Der MySQL-Server sendet die Ergebnisdaten "as is"
(wie sie sind) im Binärformat. Die Ergebnisse werden vor dem Senden nicht
zu Zeichenketten serialisiert. Die Client-Bibliotheken empfangen die
binären Daten und versuchen, die Werte in geeignete PHP-Datentypen
umzuwandeln. Zum Beispiel werden Ergebnisse aus einer
SQL-<literal>INT</literal>-Spalte als PHP-Integer-Variablen bereitgestellt.
</para>
<para>
<example>
<title>Native Datentypen</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
/* Nicht-vorbereitete Anweisung */
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT, label TEXT)");
$mysqli->query("INSERT INTO test(id, label) VALUES (1, 'PHP')");
$stmt = $mysqli->prepare("SELECT id, label FROM test WHERE id = 1");
$stmt->execute();
$result = $stmt->get_result();
$row = $result->fetch_assoc();
printf("id = %s (%s)\n", $row['id'], gettype($row['id']));
printf("label = %s (%s)\n", $row['label'], gettype($row['label']));
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
id = 1 (integer)
label = PHP (string)
]]>
</screen>
</example>
</para>
<para>
Dieses Verhalten unterscheidet sich von nicht-vorbereiteten Anweisungen.
Standardmäßig geben nicht-vorbereitete Anweisungen alle Ergebnisse als
Zeichenketten zurück. Diese Vorgabe kann mit einer Verbindungsoption
geändert werden. Wenn diese Verbindungsoption verwendet wird, gibt es keine
Unterschiede.
</para>
<para>
<emphasis role="bold">Ergebnisse über gebundene Variablen abrufen</emphasis>
</para>
<para>
Ergebnisse von vorbereiteten Anweisungen können entweder durch Binden der
Ausgabevariablen oder durch Anfordern eines
<classname>mysqli_result</classname>-Objekts abgerufen werden.
</para>
<para>
Die Ausgabevariablen müssen nach der Ausführung der Anweisung gebunden
werden. Für jede Spalte der Ergebnismenge der Anweisung muss eine Variable
gebunden werden.
</para>
<para>
<example>
<title>Binden der Ausgabevariablen</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
/* Nicht-vorbereitete Anweisung */
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT, label TEXT)");
$mysqli->query("INSERT INTO test(id, label) VALUES (1, 'PHP')");
$stmt = $mysqli->prepare("SELECT id, label FROM test WHERE id = 1");
$stmt->execute();
$stmt->bind_result($out_id, $out_label);
while ($stmt->fetch()) {
printf("id = %s (%s), label = %s (%s)\n", $out_id, gettype($out_id), $out_label, gettype($out_label));
}
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
id = 1 (integer), label = PHP (string)
]]>
</screen>
</example>
</para>
<para>
Vorbereitete Anweisungen geben standardmäßig ungepufferte Ergebnismengen
zurück. Die Ergebnisse der Anweisung werden nicht implizit vom Server
abgerufen und zur clientseitigen Pufferung zum Client übertragen. Die
Ergebnismenge nimmt solange Serverressourcen in Anspruch, bis alle
Ergebnisse vom Client abgerufen wurden. Es wird daher empfohlen, die
Ergebnisse frühzeitig abzurufen. Wenn ein Client nicht alle Ergebnisse
abrufen kann oder der Client die Anweisung schließt, bevor er alle Daten
geholt hat, müssen die Daten implizit mit <literal>mysqli</literal>
abgerufen werden.
</para>
<para>
Mit <methodname>mysqli_stmt::store_result</methodname> ist es auch möglich,
die Ergebnisse einer vorbereiteten Anweisung zu puffern.
</para>
<para>
<emphasis role="bold" >Abrufen der Ergebnisse über die
mysqli_result-Schnittstelle</emphasis>.
</para>
<para>
Anstatt gebundene Ergebnisse zu verwenden, können die Ergebnisse auch über
die mysqli_result-Schnittstelle abgerufen werden.
<methodname>mysqli_stmt::get_result</methodname> gibt eine gepufferte
Ergebnismenge zurück.
</para>
<para>
<example>
<title>Verwendung von mysqli_result zum Abrufen von Ergebnissen</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
/* Nicht-vorbereitete Anweisung */
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT, label TEXT)");
$mysqli->query("INSERT INTO test(id, label) VALUES (1, 'PHP')");
$stmt = $mysqli->prepare("SELECT id, label FROM test WHERE id = 1");
$stmt->execute();
$result = $stmt->get_result();
var_dump($result->fetch_all(MYSQLI_ASSOC));
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
array(1) {
[0]=>
array(2) {
["id"]=>
int(1)
["label"]=>
string(3) "PHP"
}
}
]]>
</screen>
</example>
</para>
<para>
Die Verwendung der <classname>mysqli_result</classname>-Schnittstelle
bietet den zusätzlichen Vorteil einer flexiblen clientseitigen Navigation
in der Ergebnismenge.
</para>
<para>
<example>
<title>Gepufferte Ergebnismenge für flexibles Auslesen</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
/* Nicht-vorbereitete Anweisung */
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT, label TEXT)");
$mysqli->query("INSERT INTO test(id, label) VALUES (1, 'PHP'), (2, 'Java'), (3, 'C++')");
$stmt = $mysqli->prepare("SELECT id, label FROM test");
$stmt->execute();
$result = $stmt->get_result();
for ($row_no = $result->num_rows - 1; $row_no >= 0; $row_no--) {
$result->data_seek($row_no);
var_dump($result->fetch_assoc());
}
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
array(2) {
["id"]=>
int(3)
["label"]=>
string(3) "C++"
}
array(2) {
["id"]=>
int(2)
["label"]=>
string(4) "Java"
}
array(2) {
["id"]=>
int(1)
["label"]=>
string(3) "PHP"
}
]]>
</screen>
</example>
</para>
<para>
<emphasis role="bold">Maskierung und SQL-Injection</emphasis>
</para>
<para>
Die gebundenen Variablen werden getrennt von der Abfrage an den Server
gesendet und können diese daher nicht beeinflussen. Der Server verwendet
diese Werte erst zum Zeitpunkt der der Ausführung, nachdem die
Anweisungsvorlage geparst wurde. Die gebundenen Parameter müssen nicht
müssen nicht maskiert werden, da sie nie direkt in die Abfragezeichenkette
eingefügt werden. Dem Server muss der Typ der gebundenen Variablen
mitgeteilt werden, um eine geeignete Umwandlung zu ermöglichen. Siehe
<methodname>mysqli_stmt::bind_param</methodname> für weitere Informationen.
</para>
<para>
Diese Trennung wird oft als die einzige Möglichkeit angesehen, sich gegen
SQL-Injection zu schützen, aber tatsächlich kann das gleiche Maß an
Sicherheit auch mit nicht-vorbereiteten Anweisungen erreicht werden, wenn
alle Werte korrekt formatiert sind. Es ist wichtig, zu beachten, dass eine
korrekte Formatierung nicht dasselbe ist wie die Maskierung, und mehr Logik
beinhaltet. Daher sind vorbereitete Anweisungen einfach ein bequemerer und
weniger fehleranfälliger Ansatz, um dieses Niveau an Datenbanksicherheit zu
erreichen.
</para>
<para>
<emphasis role="bold">Clientseitige Emulation vorbereiteter Anweisungen</emphasis>
</para>
<para>
Die API enthält keine Emulation für die clientseitige Emulation von
vorbereiteten Anweisungen.
</para>
<para>
<emphasis role="bold">Siehe auch</emphasis>
</para>
<para>
<simplelist>
<member><methodname>mysqli::__construct</methodname></member>
<member><methodname>mysqli::query</methodname></member>
<member><methodname>mysqli::prepare</methodname></member>
<member><methodname>mysqli_stmt::prepare</methodname></member>
<member><methodname>mysqli_stmt::execute</methodname></member>
<member><methodname>mysqli_stmt::bind_param</methodname></member>
<member><methodname>mysqli_stmt::bind_result</methodname></member>
</simplelist>
</para>
</section>
<section xml:id="mysqli.quickstart.stored-procedures">
<title>Gespeicherte Prozeduren (Stored Procedures)</title>
<para>
Die MySQL-Datenbank unterstützt gespeicherte Prozeduren. Eine gespeicherte
Prozedur ist ein Unterprogramm, das im Datenbankkatalog gespeichert ist.
Anwendungen können die gespeicherte Prozedur aufrufen und ausführen. Um
eine gespeicherte Prozedur auszuführen, wird die SQL-Anweisung
<literal>CALL</literal> verwendet.
</para>
<para>
<emphasis role="bold">Parameter</emphasis>
</para>
<para>
In Abhängigkeit von der MySQL-Version können gespeicherte Prozeduren die
Parameter <literal>IN</literal>, <literal>INOUT</literal> und
<literal>OUT</literal> haben. Die mysqli-Schnittstelle selbst hat keine
speziellen Bezeichnungen für die verschiedenen Arten von Parametern.
</para>
<para>
<emphasis role="bold">Der Parameter IN</emphasis>
</para>
<para>
Die Eingabeparameter werden mit der Anweisung
<literal>CALL</literal> bereitgestellt. Bitte stellen Sie sicher, dass die
Werte korrekt maskiert sind.
</para>
<para>
<example>
<title>Aufrufen einer gespeicherten Prozedur</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT)");
$mysqli->query("DROP PROCEDURE IF EXISTS p");
$mysqli->query("CREATE PROCEDURE p(IN id_val INT) BEGIN INSERT INTO test(id) VALUES(id_val); END;");
$mysqli->query("CALL p(1)");
$result = $mysqli->query("SELECT id FROM test");
var_dump($result->fetch_assoc());
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
array(1) {
["id"]=>
string(1) "1"
}
]]>
</screen>
</example>
</para>
<para>
<emphasis role="bold">Die Parameter INOUT/OUT</emphasis>
</para>
<para>
Auf die Werte der Parameter <literal>INOUT</literal>/<literal>OUT</literal>
wird über Session-Variablen zugegriffen.
</para>
<para>
<example>
<title>Verwendung von Session-Variablen</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
$mysqli->query("DROP PROCEDURE IF EXISTS p");
$mysqli->query('CREATE PROCEDURE p(OUT msg VARCHAR(50)) BEGIN SELECT "Hi!" INTO msg; END;');
$mysqli->query("SET @msg = ''");
$mysqli->query("CALL p(@msg)");
$result = $mysqli->query("SELECT @msg as _p_out");
$row = $result->fetch_assoc();
echo $row['_p_out'];
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
Hi!
]]>
</screen>
</example>
</para>
<para>
Anwendungs- und Framework-Entwickler können gegebenenfalls eine
komfortablere API bereitstellen, die neben Session-Variablen auch das
direkte Durchsuchen von Datenbankkatalogen verwendet. Dabei sind jedoch die
Leistungseinbußen zu beachten, die eine benutzerdefinierte Lösung auf Basis
der Kataloginspektion haben kann.
</para>
<para>
<emphasis role="bold">Umgang mit Ergebnismengen</emphasis>
</para>
<para>
Gespeicherte Prozeduren können Ergebnismengen zurückgeben. Ergebnismengen,
die von einer gespeicherten Prozedur zurückgegeben werden, können mit
<methodname>mysqli::query</methodname> nicht korrekt abgerufen werden. Die
Funktion <methodname>mysqli::query</methodname> führt die Anweisung aus
und ruft, falls vorhanden, die erste Ergebnismenge in einen Puffer ab.
Gespeicherte Prozeduren können jedoch weitere Ergebnismengen zurückgeben,
die dem Benutzer verborgen sind, was dazu führt, dass
<methodname>mysqli::query</methodname> nicht die vom Benutzer erwarteten
Ergebnismengen zurückgibt.
</para>
<para>
Ergebnismengen, die von einer gespeicherten Prozedur zurückgegeben werden,
werden mit <methodname>mysqli::real_query</methodname> oder
<methodname>mysqli::multi_query</methodname> abgerufen. Beide Funktionen
ermöglichen das Abrufen einer beliebigen Anzahl von Ergebnismengen, die
von einer Anweisung wie <literal>CALL</literal> zurückgegeben werden.
Gelingt es nicht, alle Ergebnismengen abzurufen, die von einer
gespeicherten Prozedur zurückgegeben wurden, löst das einen Fehler aus.
</para>
<para>
<example>
<title>Ergebnisse von gespeicherten Prozeduren abrufen</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT)");
$mysqli->query("INSERT INTO test(id) VALUES (1), (2), (3)");
$mysqli->query("DROP PROCEDURE IF EXISTS p");
$mysqli->query('CREATE PROCEDURE p() READS SQL DATA BEGIN SELECT id FROM test; SELECT id + 1 FROM test; END;');
$mysqli->multi_query("CALL p()");
do {
if ($result = $mysqli->store_result()) {
printf("---\n");
var_dump($result->fetch_all());
$result->free();
}
} while ($mysqli->next_result());
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
---
array(3) {
[0]=>
array(1) {
[0]=>
string(1) "1"
}
[1]=>
array(1) {
[0]=>
string(1) "2"
}
[2]=>
array(1) {
[0]=>
string(1) "3"
}
}
---
array(3) {
[0]=>
array(1) {
[0]=>
string(1) "2"
}
[1]=>
array(1) {
[0]=>
string(1) "3"
}
[2]=>
array(1) {
[0]=>
string(1) "4"
}
}
]]>
</screen>
</example>
</para>
<para>
<emphasis role="bold">Verwendung von vorbereiteten Anweisungen</emphasis>
</para>
<para>
Es ist keine besondere Vorgehensweise erforderlich, wenn die Schnittstelle
für vorbereitete Anweisungen zum Abrufen von Ergebnissen aus der gleichen
gespeicherten Prozedur wie oben verwendet wird. Die Schnittstellen für
vorbereitete und nicht-vorbereitete Anweisungen sind ähnlich. Es ist zu
beachten, dass nicht jede Version des MYSQL-Servers die Vorbereitung der
SQL-Anweisung <literal>CALL</literal> unterstützt.
</para>
<para>
<example>
<title>Gespeicherte Prozeduren und vorbereitete Anweisungen</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT)");
$mysqli->query("INSERT INTO test(id) VALUES (1), (2), (3)");
$mysqli->query("DROP PROCEDURE IF EXISTS p");
$mysqli->query('CREATE PROCEDURE p() READS SQL DATA BEGIN SELECT id FROM test; SELECT id + 1 FROM test; END;');
$stmt = $mysqli->prepare("CALL p()");
$stmt->execute();
do {
if ($result = $stmt->get_result()) {
printf("---\n");
var_dump($result->fetch_all());
$result->free();
}
} while ($stmt->next_result());
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
---
array(3) {
[0]=>
array(1) {
[0]=>
int(1)
}
[1]=>
array(1) {
[0]=>
int(2)
}
[2]=>
array(1) {
[0]=>
int(3)
}
}
---
array(3) {
[0]=>
array(1) {
[0]=>
int(2)
}
[1]=>
array(1) {
[0]=>
int(3)
}
[2]=>
array(1) {
[0]=>
int(4)
}
}
]]>
</screen>
</example>
</para>
<para>
Natürlich wird auch die Verwendung der bind-API für das Abrufen von Daten
unterstützt.
</para>
<para>
<example>
<title>Gespeicherte Prozeduren und vorbereitete Anweisungen mit der bind-API</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT)");
$mysqli->query("INSERT INTO test(id) VALUES (1), (2), (3)");
$mysqli->query("DROP PROCEDURE IF EXISTS p");
$mysqli->query('CREATE PROCEDURE p() READS SQL DATA BEGIN SELECT id FROM test; SELECT id + 1 FROM test; END;');
$stmt = $mysqli->prepare("CALL p()");
$stmt->execute();
do {
if ($stmt->store_result()) {
$stmt->bind_result($id_out);
while ($stmt->fetch()) {
echo "id = $id_out\n";
}
}
} while ($stmt->next_result());
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
id = 1
id = 2
id = 3
id = 2
id = 3
id = 4
]]>
</screen>
</example>
</para>
<para>
<emphasis role="bold">Siehe auch</emphasis>
</para>
<para>
<simplelist>
<member><methodname>mysqli::query</methodname></member>
<member><methodname>mysqli::multi_query</methodname></member>
<member><methodname>mysqli::next_result</methodname></member>
<member><methodname>mysqli::more_results</methodname></member>
</simplelist>
</para>
</section>
<section xml:id="mysqli.quickstart.multiple-statement">
<title>Mehrfach-Anweisungen</title>
<para>
Bei MySQL ist es optional möglich, mehrere Anweisungen in einer
Anweisungszeile zu haben, was aber eine besondere Vorgehensweise erfordert.
</para>
<para>
Mehrfach-Anweisungen oder Mehrfach-Abfragen müssen mit
<methodname>mysqli::multi_query</methodname> ausgeführt werden. Die
einzelnen Anweisungen der Anweisungszeile werden durch Semikolon getrennt.
Anschließend müssen alle Ergebnismengen, die von den ausgeführten
Anweisungen zurückgegeben werden, abgerufen werden.
</para>
<para>
Der MySQL-Server erlaubt es, Anweisungen, die Ergebnismengen zurückgeben,
und Anweisungen, die keine Ergebnismengen zurückgeben, in einer
Mehrfach-Anweisung zu verwenden.
</para>
<para>
<example>
<title>Mehrere Anweisungen</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT)");
$sql = "SELECT COUNT(*) AS _num FROM test;
INSERT INTO test(id) VALUES (1);
SELECT COUNT(*) AS _num FROM test; ";
$mysqli->multi_query($sql);
do {
if ($result = $mysqli->store_result()) {
var_dump($result->fetch_all(MYSQLI_ASSOC));
$result->free();
}
} while ($mysqli->next_result());
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
array(1) {
[0]=>
array(1) {
["_num"]=>
string(1) "0"
}
}
array(1) {
[0]=>
array(1) {
["_num"]=>
string(1) "1"
}
}
]]>
</screen>
</example>
</para>
<para>
<emphasis role="bold">Sicherheitstechnische Überlegungen</emphasis>
</para>
<para>
Die API-Funktionen <methodname>mysqli::query</methodname> und
<methodname>mysqli::real_query</methodname> setzen kein Verbindungsflag auf
dem Server, das für die Aktivierung von Mehrfach-Abfragen benötigt wird.
Für Mehrfach-Anweisungen wird ein zusätzlicher API-Aufruf verwendet, um den
Schaden von Angriffen mit SQL-Injections zu verringern. Ein Angreifer
könnte versuchen, Anweisungen wie <literal>; DROP DATABASE mysql</literal>
oder <literal>; SELECT SLEEP(999)</literal> an das Ende einer Anweisung
anzuhängen. Wenn es dem Angreifer gelingt, SQL zur Anweisung hinzuzufügen,
aber <methodname>mysqli::multi_query</methodname> nicht verwendet wird,
führt der Server die eingeschleuste bösartige SQL-Anweisung nicht aus.
</para>
<para>
<example>
<title>SQL-Injection</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
$result = $mysqli->query("SELECT 1; DROP TABLE mysql.user");
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
PHP Fatal error: Uncaught mysqli_sql_exception: You have an error in your SQL syntax;
check the manual that corresponds to your MySQL server version for the right syntax to
use near 'DROP TABLE mysql.user' at line 1
]]>
</screen>
</example>
</para>
<para>
<emphasis role="bold">Vorbereitete Anweisungen</emphasis>
</para>
<para>
Die Verwendung der Mehrfach-Anweisung wird bei vorbereiteten Anweisungen
nicht unterstützt.
</para>
<para>
<emphasis role="bold">Siehe auch</emphasis>
</para>
<para>
<simplelist>
<member><methodname>mysqli::query</methodname></member>
<member><methodname>mysqli::multi_query</methodname></member>
<member><methodname>mysqli::next_result</methodname></member>
<member><methodname>mysqli::more_results</methodname></member>
</simplelist>
</para>
</section>
<section xml:id="mysqli.quickstart.transactions">
<title>API-Unterstützung für Transaktionen</title>
<para>
Je nach verwendeter Speicher-Engine unterstützt der MySQL-Server
Transaktionen. Seit MySQL 5.5 ist InnoDB die Standard-Speicher-Engine.
InnoDB bietet volle Unterstützung für ACID-Transaktionen.
</para>
<para>
Transaktionen können entweder mit SQL oder über API-Aufrufe gesteuert
werden. Es wird empfohlen, API-Aufrufe zu verwenden, um den
<literal>autocommit</literal>-Modus zu aktivieren oder zu deaktivieren und
um Transaktionen zu übertragen (Commit) und rückgängig zu machen
(Rollback).
</para>
<para>
<example>
<title>Einstellen des <literal>autocommit</literal>-Modus mit SQL und über die API</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
/* Empfohlen: Verwendung der API zur Steuerung von Transaktionseinstellungen */
$mysqli->autocommit(false);
/* Wird von Replikations- und Load-Balancing-Plugins nicht erkannt und überwacht */
$mysqli->query('SET AUTOCOMMIT = 0');
]]>
</programlisting>
</example>
</para>
<para>
Optionale Zusatzpakete, &zb; das Replikations- und Load-Balancing-Plugin,
können API-Aufrufe problemlos überwachen. Wenn Transaktionen über
API-Aufrufe gesteuert werden, bietet das Replikations-Plugin
transaktionsbezogenes Load-Balancing. Transaktionsbezogenes
Load-Balancing ist nicht verfügbar, wenn SQL-Anweisungen verwendet werden,
um den <literal>autocommit</literal>-Modus zu setzen oder Transaktionen zu
übertragen oder rückgängig zu machen.
</para>
<para>
<example>
<title>Übertragen und Zurücksetzen (Commit und Rollback)</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
$mysqli->autocommit(false);
$mysqli->query("INSERT INTO test(id) VALUES (1)");
$mysqli->rollback();
$mysqli->query("INSERT INTO test(id) VALUES (2)");
$mysqli->commit();
]]>
</programlisting>
</example>
</para>
<para>
Es ist zu beachten, dass der MySQL-Server nicht alle Anweisungen
zurücknehmen kann. Einige Anweisungen führen zu einem impliziten Commit.
</para>
<para>
<emphasis role="bold">Siehe auch</emphasis>
</para>
<para>
<simplelist>
<member><methodname>mysqli::autocommit</methodname></member>
<member><methodname>mysqli::begin_transaction</methodname></member>
<member><methodname>mysqli::commit</methodname></member>
<member><methodname>mysqli::rollback</methodname></member>
</simplelist>
</para>
</section>
<section xml:id="mysqli.quickstart.metadata">
<title>Metadaten</title>
<para>
Eine MySQL-Ergebnismenge enthält Metadaten. Die Metadaten beschreiben die
Spalten, die in der Ergebnismenge gefunden werden. Alle von MySQL
gesendeten Metadaten sind über die <literal>mysqli</literal>-Schnittstelle
verfügbar. Die Erweiterung führt keine oder vernachlässigbare Änderungen an
den Informationen durch, die sie empfängt. Die Unterschiede zwischen den
MySQL-Server-Versionen werden nicht angeglichen.
</para>
<para>
Auf die Metadaten wird über die
<classname>mysqli_result</classname>-Schnittstelle zugegriffen.
</para>
<para>
<example>
<title>Zugriff auf die Metadaten der Ergebnismenge</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
$result = $mysqli->query("SELECT 1 AS _one, 'Hello' AS _two FROM DUAL");
var_dump($result->fetch_fields());
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
array(2) {
[0]=>
object(stdClass)#3 (13) {
["name"]=>
string(4) "_one"
["orgname"]=>
string(0) ""
["table"]=>
string(0) ""
["orgtable"]=>
string(0) ""
["def"]=>
string(0) ""
["db"]=>
string(0) ""
["catalog"]=>
string(3) "def"
["max_length"]=>
int(1)
["length"]=>
int(1)
["charsetnr"]=>
int(63)
["flags"]=>
int(32897)
["type"]=>
int(8)
["decimals"]=>
int(0)
}
[1]=>
object(stdClass)#4 (13) {
["name"]=>
string(4) "_two"
["orgname"]=>
string(0) ""
["table"]=>
string(0) ""
["orgtable"]=>
string(0) ""
["def"]=>
string(0) ""
["db"]=>
string(0) ""
["catalog"]=>
string(3) "def"
["max_length"]=>
int(5)
["length"]=>
int(5)
["charsetnr"]=>
int(8)
["flags"]=>
int(1)
["type"]=>
int(253)
["decimals"]=>
int(31)
}
}
]]>
</screen>
</example>
</para>
<para>
<emphasis role="bold">Vorbereitete Anweisungen</emphasis>
</para>
<para>
Auf die Metadaten von Ergebnismengen, die mit vorbereiteten Anweisungen
erstellt wurden, wird auf die gleiche Weise zugegriffen. Ein geeignetes
<classname>mysqli_result</classname>-Handle wird von
<methodname>mysqli_stmt::result_metadata</methodname> zurückgegeben.
</para>
<para>
<example>
<title>Metadaten der vorbereiteten Anweisungen</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
$stmt = $mysqli->prepare("SELECT 1 AS _one, 'Hello' AS _two FROM DUAL");
$stmt->execute();
$result = $stmt->result_metadata();
var_dump($result->fetch_fields());
]]>
</programlisting>
</example>
</para>
<para>
<emphasis role="bold">Siehe auch</emphasis>
</para>
<para>
<simplelist>
<member><methodname>mysqli::query</methodname></member>
<member><methodname>mysqli_result::fetch_fields</methodname></member>
</simplelist>
</para>
</section>
</chapter>