Die in PHP 5 integrierte Datenbank SQLite bietet vor allem auf Shared-Hosting-Umgebungen hervorragende Möglichkeiten für schnelle Datenbank-Applikationen.
Da SQLite in PHP 5 integriert ist, benötigt es keine externen Dienste wie zum Beispiel bei der Kombination MySQL und PHP. Das bringt bei der Datenbankprogrammierung eine Menge Vorteile mit sich, lassen sich so doch sehr leicht Applikationen erstellen und in eine Anwendung integrieren. Die Einrichtung einer neuen Datenbank mit SQLite ist einfach und erfordert keinen Systemadministrator mit besonderen Privilegien.
Die Programmierung von SQLite kann wie beim großen Bruder sowohl objektorientiert als auch mit Prozeduren erfolgen. Allerdings bringt die Tatsache, dass SQLite keinen Serverprozess hat, auch einige Nachteile mit sich. Dazu zählen Probleme beim gleichzeitigem Zugriff, das Fehlen eines beständigen Cachespeichers für Abfragen und Skalierungsprobleme beim Umgang mit sehr großen Datenvolumen.
Auch erlaubt SQLite keine unmittelbare Verarbeitung von Binärdaten. Diese müssen beim Speichern zuerst codiert und nach dem Auslesen wieder decodiert werden. Das größte Problem tritt jedoch beim konkurrierenden Zugriff mehrerer Nutzer auf die Datenbank auf: Während andere relationale Datenbanksysteme bei Transaktionen einzelne Tabellen oder auch nur Zeilen sperren, ist bei SQLite beim Einfügen die gesamte Datenbank gesperrt. Das kann bei parallelen Zugriffen zu spürbaren Geschwindigkeitseinbußen führen.
Bei eigenständigen Web-Anwendungen, bei denen es viele Lesezugriffe bei relativ wenigen Schreibzugriffen gibt, fällt dieser Nachteil jedoch nicht so dramatisch ins Gewicht. In solchen Anwendungsszenarien bietet SQLite eine gute Performance.
»Datenbank anlegen«
Im folgenden Workshop sollen verschiedene Varianten des Zugriffs auf eine SQLite-Datenbank anhand eines Beispiels demonstriert werden. Folgendes Szenario liegt dabei zu Grunde: Ein CSV-File mit diversen Nachrichten soll in eine Datenbank geschrieben werden. Diese Informationen sollen anschließend im Rahmen eine Anwendung selektiv auf Webseiten ausgegeben werden.
Die Unterschiede zu serverbasierten Datenbanken und vor allem die Einfachheit demonstriert am besten die Prozedur, die zum Anlegen einen neuen Datenbank notwendig ist. Es geht dabei einfach um das Anlegen einer speziell formatierten Datei in einem Verzeichnis Ihrer Wahl.
Zum Erzeugen einer neuen Datenbank müssen Sie nur versuchen, eine zu öffnen. Existiert noch keine Datenbank mit diesem Namen, wird eine neue angelegt. In der Praxis sieht das so aus:
<?php
$db = new SQLiteDatabase( "./mediennews.db", 0666, &$error) or die("Fehler $error");
unset($db);
?>
Mit dem zweiten Parameter für den Konstruktor kann man übrigens die Zugriffsrechte für die Datenbank festlegen. In der neuen Datenbank soll eine Tabelle news für die einzulesenden Nachrichten erstellt werden. Dazu ist folgender Code erforderlich:
<?php
$query_erstellen = "CREATE TABLE news (id INTEGER PRIMARY KEY, title, rubrik, inhalt );
$db->query($query_erstellen);
?>
Das System der Feldtypen, mit denen man bei SQLite arbeiten kann, ist ebenfalls sehr einfach gestrickt. Es gibt deren zwei: INTEGER für die Speicherung von Zahlen und eine zweite Form, die in etwa mit dem VARCHAR-Typ von MySQL vergleichbar ist. In einem solchen Feld lassen sich jedoch mehr als 255 Zeichen speichern. Man kann ein INTEGER-Feld automatisch inkrementieren, indem man an die Felddefinition den Zusatz PRIMARY KEY anhängt. Es können auch mehrere CREATE TABLE-Abfragen mit einem Funktionsaufruf an die Methode query() ausgeführt werden.
»Daten einlesen«
In die erstellte Tabelle sollen nun die Informationen, die in dem File mediennews.csv abgelegt sind, übertragen werden. Die einzelnen Felder sind mit einem Semikolon separiert. Dies ist wichtig, weil in der entsprechenden PHP-Funktion zum Einlesen der Daten dieser Parameter angegeben werden muss. Vor dem Einlesen muss die Datenbank geöffnet werden. Die eigentliche Einleseprozedur sieht dann so aus:
$handle = fopen ("mediennews.csv","r");
while ( ($data = fgetcsv ($handle, 1000, ";")) !== FALSE ) {
$sql = "INSERT INTO news VALUES (NULL,'$data[0]', '$data[1]', '$data[2]')";
sqlite_query($dbhandle,$sql);
}
fclose ($handle);
Nun befinden sich alle erfassten Datensätze in der SQLite-Datenbank, und Sie können im nächsten Schritt daran gehen, diese selektiv auszulesen und daraus eine HTML-Seite zu generieren. Dazu müssen Sie einen Blick auf die Struktur der Tabelle werfen. Das erste Feld id ist qua Definition mit einem fortlaufenden Zähler belegt. Dann gibt es in der Tabelle noch die Felder mit der Headline der Nachricht (title), einer Rubrik (rubrik) und das Feld mit der eigentlichen Nachricht (inhalt). Um nun eine Webseite mit den Nachrichten einer ausgewählten Rubrik zu generieren, müssen Sie eine SQL-Abfrage aufbauen, in der Sie via WHERE eine bestimmte Rubrik selektieren und diese dann als SQLite-Query abschicken:
echo "<h1>TV-News</h1>";
$db = sqlite_open("mediennews.db", 0666, $error) or die("Fehler $error");
$sql = 'SELECT * FROM news WHERE rubrik="TV" ORDER BY rubrik DESC';
$result = sqlite_query($db, $sql);
if(sqlite_num_rows($result) > 0){
while($row = sqlite_fetch_array($result)) {
echo '<p>'.$row['title'].'</p>'. '<p>' .stripslashes($row ['inhalt']) .'</p>';
}
}
Aus diesen Basiskonstruktionen für das Anlegen, Schreiben und Auslesen kann man die Einfachheit der Programmierung mit SQLite ersehen.
»Daten eingeben«
Alternativ kann man die Datenbank auch über ein Formular mit Inhalten füllen. Ein Formular für die Eingabe kann zum Beispiel so aussehen:
echo <<<FORMULAR
<form action="eintragen.php" method="post">
Headline:<br/>
<input type="text" style="width:350px" name="head"/>
<br/>
Artikel: <br/>
<textarea style="width:350px; height:100px" name="text">
</textarea>
<br/>
Rubrik:<br />
<input type="text" style="width:150px" name="rubrik"/>
<br/><br/>
<input type="submit" name="Send" value="Eintragen"/>
</form>
FORMULAR;
Die per POST-Variable übergebenen Daten werden dann so in die Tabelle eingetragen:
$a = $_POST['head'];
$b = $_POST['rubrik'];
$c = $_POST['text'];
$db = sqlite_open("mediennews.db", 0666, $error) or die("Fehler $error");
$sql = "INSERT INTO news VALUES (NULL, '$a', '$b', '$c')";
sqlite_query($db, $sql);
echo "Datensatz eingetragen<br />
<br />";
Im Prinzip unterscheidet sich die Programmierung einer SQLite-Datenbank in diesem Bereich kaum von der einer anderen Variante wie zum Beispiel MySQL. Man muss allerdings berücksichtigen, dass nicht der gesamte Umfang der Query-Language unterstützt wird. Auf der Seite www.sqlite.org/lang.html ist die unterstützte Syntax dokumentiert.
»Benutzerdefinierte Funktionen«
Quasi als Ausgleich für diese Defizite bietet Ihnen SQLite jedoch die Möglichkeit, eigene Funktionen zu schreiben und diese in Ihren SQL-Abfragen zu verwenden. Mit dem Befehl sqlite_create_ function() können Sie eine PHP-Funktion als so genannte User Defined Function (UDF) erzeugen und diese dann direkt in SQL-Befehlen nutzen. Im folgenden Beispiel wird eine Funktion definiert, die die md5-Summe eines Strings berechnet und dann rückwärts ausliefert. Wenn der SQL-Befehl durchgeführt wird, liefert er den Wert der Spalte filename durch die Funktion transformiert zurück. Die Daten, die in $rows stehen, enthalten also die bereits gewandelten Daten:
<?php
function md5_rueckwaerts($string) {
return strrev(md5($string));
}
sqlite_create_function($db, 'md5rw','md5_rueckwaerts', 1);
$rows = sqlite_array_query($db, 'SELECT md5rw(filename) from files');
?>
Durch diese Technik lässt sich zum Beispiel die komplizierte Abarbeitung eines kompletten Abfrageergebnisses mit einer foreach()-Schleife vermeiden. PHP registriert außerdem automatisch eine spezielle Funktion mit dem Namen php, wenn eine Datenbank zum ersten Mal geöffnet wird. Diese Funktion kann genutzt werden, um eine beliebige PHP-Funktion aufzurufen. Die Codierung sieht so aus:
<?php
$rows = sqlite_array_query($db, "SELECT php('md5', filename) from files");
?>
In diesem Beispiel wird die Funktion md5() für jeden Eintrag der Spalte filename aufgerufen und das Ergebnis in $rows geschrieben.
»Fehlerbehandlung«
Die Fehlerbehandlung in SQLite ist etwas gewöhnungsbedürftig, da jede der Abfragefunktionen eine Warnung ausgeben kann. Daher ist es wichtig, den Abfragefunktionen den Silence-Operator @ voranzustellen. Um zu sehen, ob die Abfrage erfolgreich war, muss das Ergebnis der Funktionen auf den Wert FALSE überprüft werden. War sie nicht erfolgreich, können Sie mit Hilfe von sqlite_last_ error() und sqlite_error_string() einen Beschreibungstext des Fehlers erhalten. Leider ist dieser Text nicht sehr ausführlich. Der Konstruktor von SQLite könnte auch eine SQLite-Exception zurückgeben, die Sie selbst mit einem try-catch-Block abfangen müssen.
Es gibt einige Tricks, um die Leistung einer SQLite-Datenbankanwendung zu steigern. Beim Einfügen von vielen Daten in die Datenbank sind Sie für gewöhnlich nicht daran interessiert, wie viele Änderungen es in der Ergebnismenge gegeben hat. Daher lässt sich in SQLite das Zählen der Änderungen deaktivieren, was die Geschwindigkeit beim Einfügen erhöht. Sie können das Zählen von Änderungen in SQLite mit der folgenden Abfrage deaktivieren:
$db->query("PRAGMA count_changes = 0");
Man kann zudem als weiteren Trick die Art ändern, mit der SQLite die Daten auf die Festplatte schreibt. Mit dem Pragma synchronous können Sie zwischen den folgenden Modi wählen:
- OFF – SQLite schreibt überhaupt keine Daten auf die Festplatte; das Betriebssystem muss sich darum kümmern.
- ON/NORMAL (Standard) – In diesem Modus sorgt SQLite regelmäßig mit dem Systemaufruf fsync() dafür, dass die Daten auf die Festplatte geschrieben werden.
- FULL – SQLite verwendet den Systemaufruf fsync() öfter, um damit das Risiko des Datenverlusts bei einem Systemausfall zu senken.
Wenn sehr viel von der SQL-Datenbank gelesen wird, kann ein höherer Cache sehr nützlich sein. Die Standardgröße liegt bei 2000 Seiten, aber Sie können sie mit der folgenden Abfrage erhöhen:
PRAGMA cache_size=5000;
Diese Einstellung gilt nur für die aktuelle Sitzung und der gewählte Wert geht verloren, wenn die Verbindung zur Datenbank unterbrochen wird.
Es gibt noch einige andere Kniffe in SQLite, zum Beispiel wie man die Datenbankstruktur abfragt. Die Antwort darauf ist simpel:
SELECT * FROM sqlite_master
Diese Abfrage gibt pro Datenbankobjekt (Tabelle, Index und Trigger) ein Element mit den folgenden Informationen zurück: Objekttyp und -name, die Tabelle, mit der das Objekt verbunden ist – das ist nur sinnvoll bei Indizes und Triggern –, eine ID und die SQL-DDL-Abfrage zum Erstellen des Objekts.
Zu guter Letzt sollte man noch einen Blick auf die so genannte Views werfen, eine SQL-Funktion, mit der man benutzerdefinierte Abfragen vereinfachen kann. Wollen Sie zum Beispiel ein View doc_body_id erstellen, das nur die Felder id und body der Tabelle document enthält, können Sie die folgende Abfrage ausführen:
CREATE VIEW doc_id_body AS SELECT id, body FROM document;
Nachdem das View erstellt wurde, kann es in SQL-Abfragen wie eine richtige Tabelle verwendet werden. Die folgende Abfrage zum Beispiel benutzt das View und gibt die id- und body-Felder der ersten beiden Einträge unserer Tabelle document zurück:
SELECT * FROM doc_id_body LIMIT 2;
Natürlich bringt ein View in diesem Fall mit einer einzigen Tabelle wenig Vorteile, aber bei komplexen Abfragen, die auf mehrere Tabellen zurückgreifen, sind Views sehr nützlich.
»Sicherungsmaßnahmen«
Für SQLite-Datenbankdateien wird in der Regel die Endung SQLITE verwendet. Sie sollten jedoch sicherstellen, dass darauf nicht direkt zugegriffen werden kann und sie nicht in einer Verzeichnisauflistung erscheinen. Um die Dateien zu verstecken, legen Sie am besten in dem Verzeichnis eine .htaccess-Datei an.
Schließlich gibt es noch das Problem, dass nach vielen Schreib- und Löschvorgängen die Größe der Datenbank gleich bleibt, selbst wenn mehr als die Hälfte der Datensätze gelöscht wurde. Die ist kein Fehler. Führen Sie in solchen Fällen einfach die Anweisung vacuum, gefolgt vom Tabellennamen aus. SQLite reorganisiert anschließend die Tabelle und bereinigt sie.
SQLite-Funktionen und MethodenDie nachfolgende Liste zeigt die Funktionen und Methoden, die Ihnen für die Programmierung von SQLite-Anwendungen zur Verfügung stehen. $result->seek() – sqlite_seek() Sucht eine Zeile in der Ergebnismenge. Der einzige Parameter ist die nullbasierte Nummer des Eintrags in der Menge. Diese Funktion kann nur bei gepufferten Ergebnismengen benutzt werden. $result->rewind() – sqlite_rewind() Setzt den Ergebniszeiger auf den ersten Eintrag in der Ergebnismenge. Diese Funktion kann nur bei gepufferten Ergebnismengen benutzt werden. $result->next() – sqlite_next() Beim Aufruf dieser Funktion erfolgt ein Sprung zum nächsten Eintrag in der ausgelesenen Ergebnismenge. $result->prev() – sqlite_prev() Geht zum vorherigen Eintrag in der Ergebnismenge. Diese Funktion kann nur bei gepufferten Ergebnismengen benutzt werden. $result->valid() – sqlite_valid() –sqlite_has_more() Ermittelt, ob es noch weitere Einträge in der Ergebnismenge gibt. $result->hasPrev() – sqlite_has_prev() Die Funktion gibt an, ob es einen vorhergehenden Eintrag gibt. Diese Funktion kann jedoch nur bei gepufferten Ergebnismengen benutzt werden. |