Sessions in der Datenbank – The dirty way
Euer erster Lösungsansatz als ihr „Webcluster“ gelesen habt war möglicherweise die Datenbank. Klar sie ist von allen Servern aus erreichbar und egal wie sie aufgebaut ist – Cluster, Master-Slave, … – liefert sie konsistente Daten ohnehin schon die ganze Zeit, warum nicht auch für Sessions verwenden.
Natürlich geht das. Alles was ihr braucht ist einen eigenen Session-Handler. Der könnte zum Beispiel so aussehen:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
<?php class Session implements \SessionHandlerInterface { /** * Könnt ihr natürlich auch über den Konstruktor übergeben. */ const DB_TABLE = 'sessions'; /** * Zugang zur Datenbank * * @var \PDO */ private $db; public function __construct(\PDO $db) { $this->db = $db; // Wir müssen unseren Session-Hander dem System bekannt machen session_set_save_handler($this, true); // Wir starten auch gleich die Session session_start(); } public function open($save_path, $name) { // Hier müssen wir nichts tun, die Datenbankverbindung bekommen wir schon über den Konstruktor. } public function close() { // Hier ist ebenfalls nichts zu tun. Auch das Schließen der DB-Verbindung findet anderswo statt. } public function read($session_id) { $data = null; $stmt = $this->db->prepare('SELECT data FROM '.self::DB_TABLE.' WHERE id = :id'); if ($stmt->execute([':id' => $session_id])) { $data = $stmt->fetchColumn(); } else { // Fehlerbehandlung } // Was ihr hier zurück gebt, steht anschließend global in $_SESSION zur Verfügung return $data; } public function write($session_id, $session_data) { // In $session_data steht alles was in $_SESSION abgelegt wurde. Wir speichern dies in der DB // Wir verwenden REPLACE um sowohl neue als auch existierende Sessions abzudecken. // Natürlich ginge auch INSERT INTO ... ON DUPLICATE KEY UPDATE ... $stmt = $this->db->prepare('REPLACE INTO '.self::DB_TABLE.' SET id = :id, data = :data'); $stmt->execute([':id' => $session_id, ':data' => $session_data]); } public function destroy($session_id) { // Die Session soll explizit zerstört werden. // Dies geschieht nicht automatisch, sodern nur beim Aufruf von session_destroy(); Zum Beispiel beim Logout. // Wir löschen also die Daten. $stmt = $this->db->prepare('DELETE FROM '.self::DB_TABLE.' WHERE id = :id'); $stmt->execute(); } public function gc($maxlifetime) { // Ab und zu (je nach Konfiguration) wird von PHP automatisch die Garbage Collection für Sessions getriggert. // Dabei wird diese Methode aufgerufen. Wir müssen hier also alle abgelaufenen Sessions löschen. // Hierzu bekommen wir die konfigurierte Laufzeit der Session als Parameter $maxlifetime in Sekunden übergeben. $expired = new \DateTime($maxlifetime.' seconds ago'); $this->db->query('DELETE FROM '.self::DB_TABLE.' WHERE created < "'.$expired->format(\DateTime::ISO8601).'"'); } } |
Die zugehörige Tabelle sähe so aus:
1 2 3 4 5 6 |
CREATE TABLE IF NOT EXISTS `sessions` ( `id` varchar(32) NOT NULL, `created` datetime DEFAULT NULL, `data` longtext, PRIMARY KEY (`id`) ); |
Engine und Charset könnt ihr nach euren Gegebenheiten anpassen und longtext
für data
ist möglicherweise auch übertrieben. Hier müsst ihr selbst entscheiden, welche Datenmengen ihr erwartet.
Anstatt einem session_start();
macht ihr jetzt new Session($dbConnection);
Der Rest bleibt gleich.
Die Probleme mit Sessions in der Datenbank sind aber leider auch sehr deutlich. Eine Session wird bei jedem Request sowohl geladen als auch gespeichert und sie enthält für jeden Benutzer andere Daten. Ihr habt also sehr viele Schreibzugriffe (generell schlecht für eine Datenbank) mit sich ständig ändernden Daten (Cache wird invalidiert). Da ohnehin jeder Benutzer eine eigene Session hat, ergeben sich auch keine Möglichkeiten für effizientes Caching.
Aufgrund der erwähnten Performanceprobleme rate ich euch von einer Datenbanklösung dringend ab!
Vorteile die Session in der Datenbank zu speichern
- Die Datenbank ist als Speichermedium sowieso vorhanden
- Keine „Cluster-Problematik“
Nachteile die Datenbank mit der Session zu behelligen
- Performance!
- Overhead durch Netzwerkabfragen
- Nicht trivial umzusetzen
Weiter mit Sessions mit Memcache