Bei der Einführung von TYPO3 4.5 Mitte Januar wurden die neuen Prepared-Statements schon angekündigt. In dem SlideShare von Patrick Lobacher werden diese besonderen Queries als deutlich optimierte Datenbankabfragen angepriesen. Bei der Googlesuche nach „prepare_SELECTquery“ habe ich nur 160 Ergebnisse und eine Suche hier bei typo3blogger resultiert in einem leeren Ergebnis. Ich denke wir sollten da mal ein bisschen Licht rein bringen.
Was aber ist der Unterschied zwischen normalen SELECT- und diesen PREPARED-Abfragen? Zu allererst sollte man wissen, was der MySQL-Server mit einer SELECT-Abfrage macht. Diese Abfragen werden nicht einfach ausgeführt sondern müssen erst durch die verschiedenen Arbeitsschritten von Parsern und Optimierern.
Wenn es Euch interessiert könnt Ihr Euch über die Shell mit dem mysql-Befehl auf den Server einloggen und das Profiling aktivieren:
SET profiling = 1;
Alle nun über die MySQL-Shell eingegebenen Abfragen werden protokolliert und können sehr detailliert analysiert werden. Mach nun mal ein einfaches SELECT auf die fe_users:
SELECT * FROM fe_users;
Führt danach direkt noch diesen Befehl aus:
SHOW profile CPU FOR QUERY 1;
In dieser Aufstellung seht Ihr nun, wie lange der MySQL-Server für jede durchgeführte Aufgabe gebraucht hat. Bei mir sind es 16 Aufgaben, die der Server abarbeiten musste. Nun stellt Euch mal folgendes PHP-Konstrukt vor, dass alle Inhaltselemente zu den entsprechenden Seiten raussucht:
$res = $GLOBALS['TYPO3_DB']->exec_SELECTquery( 'SQL_NO_CACHE uid, title', 'pages', '1=1 ' . $this->cObj->enableFields('pages'), '', '', '' ); while($page = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) { $content .= ' |
‚ . $page[‚title‘] . ‚:
- ‚ . $data[‚header‘] . ‚
- ‚; $res2 = $GLOBALS[‚TYPO3_DB‘]->exec_SELECTquery( ‚SQL_NO_CACHE uid, header‘, ‚tt_content‘, ‚pid = ‚ . $page[‚uid‘] . ‚ ‚ . $this->cObj->enableFields(‚tt_content‘), “, “, “ ); while($data = $GLOBALS[‚TYPO3_DB‘]->sql_fetch_assoc($res2)) { $content .= ‚
‚; } $content .= ‚
‚; }
Solche Konstrukte findet man immer wieder in Extensions vor. Allerdings wird hier für jede Seite eine neue Anfrage an den Server gesendet. Zwar hat der MySQL-Server einen Cache, auf den er zugreifen kann, aber bis zu dieser Entscheidung muss der MySQL-Server immer wieder bestimmte Aufgaben zwecks Überprüfung durchführen.
Mit den nun eingeführten PREPARED-Abfragen, können wir dem MySQL-Server eine vorbereitete Abfrage mit Platzhaltern senden. Diese Abfrage wird EINMAL vom Server verarbeitet und alle Abfragen danach (mit unterschiedlichen Platzhaltern) können direkt und OHNE weitere Operationen und Parsen abgearbeitet werden. Statt den vorherigen 16 Aufgaben müssen nun vielleicht nur noch 6 oder 8 Aufgaben ausgeführt werden. Dieser Wegfall von Aufgaben wirkt sich natürlich positiv auf die Verarbeitungszeit aus.
TYPO3 bringt seit der 4.5er Version eine neue Klasse „class.t3lib_db_preparedstatement.php“ mit, mit der diese vorbereiteten Abfragen realisiert werden können. Doch Achtung! Schaut Euch die Datei mal an. Überall sind Verweise auf die ganz normalen SELECT-Methoden der t3lib_db zu finden. Wie kann das also schneller sein? Die Antwort ist einfach: Gar nicht! Ich habe es mehrfach getestet. Aber ein Eintrag in der TYPO3-Dev-Liste lässt hoffen. Ich habe erfahren, dass TYPO3 wohl in Zukunft auf mysqli wechseln möchte. Diese Objektorientierte Art des MySQL-Zugriffs bringt unteranderem auch Methoden mit, um diese PREPARED-Statements zu verarbeiten. Die neue TYPO3-Klasse ist derzeit also nur ein Dummy und wird, wenn es dann mal so weit ist auch tatsächlich einen Performanceschub bringen.
Hier mal das gleiche Beispiel von oben auf Basis von PREPARED-Statements:
$GLOBALS['TYPO3_DB']->sql_query('PREPARE contentStmt FROM " SELECT SQL_NO_CACHE uid, header FROM tt_content WHERE pid = ? ' . $this->cObj->enableFields('tt_content') . ' "'); $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery( 'SQL_NO_CACHE uid, title', 'pages', '1=1 ' . $this->cObj->enableFields('pages'), '', '', '' ); //t3lib_div::devLog('db', 'db', -1, array($res)); while($page = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) { $content .= ' |
‚ . $page[‚title‘] . ‚:
- ‚ . $data[‚header‘] . ‚
- ‚; $GLOBALS[‚TYPO3_DB‘]->sql_query(‚SET @uid = ‚ . $page[‚uid‘]); $res2 = $GLOBALS[‚TYPO3_DB‘]->sql_query(‚ EXECUTE contentStmt USING @uid; ‚); while($data = $GLOBALS[‚TYPO3_DB‘]->sql_fetch_assoc($res2)) { $content .= ‚
‚; } $content .= ‚
‚; } $GLOBALS[‚TYPO3_DB‘]->sql_query(‚DEALLOCATE PREPARE contentStmt‘);
Ihr seht, dass prepare_SELECTquery ein Object und nicht mehr einen MySQL-Link zurück gibt. Ansonsten bleiben die übergebenen Variablen für fields, table und where nahezu gleich. Bis auf den Punkt in der WHERE-Clause: Hier könnt Ihr nun mit dem Doppelpunkt-Parameter Platzhalter definieren, die Ihr später mal ersetzen wollt (:uid). Später könnt Ihr mit der Methode „execute()“ diese Doppelpunkt-Platzhalter mit Werten ersetzen und die Daten mit der Methode „fetch()“ abrufen. Alle vorbereiteten Abfragen werden im MySQL-Cache vorbehalten und nehmen Platz im Speicher ein. Um diesen wieder freizugeben solltet Ihr mit der Methode „free()“ diesen Platz wieder freigeben.
An dieser Stelle will ich Euch noch einen Punkt beschreiben mit dem man wirklich mal Speed in seine Abfragen reinbringen kann. Ich möchte Euch aber auch noch darauf aufmerksam machen, dass diese hier gezeigte Variante nicht DBAL kompatibel ist:
$GLOBALS['TYPO3_DB']->sql_query('PREPARE contentStmt FROM " SELECT SQL_NO_CACHE uid, header FROM tt_content WHERE pid = ? ' . $this->cObj->enableFields('tt_content') . ' "'); $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery( 'SQL_NO_CACHE uid, title', 'pages', '1=1 ' . $this->cObj->enableFields('pages'), '', '', '' ); //t3lib_div::devLog('db', 'db', -1, array($res)); while($page = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) { $content .= '<p>' . $page['title'] . ':</p><ul>'; $GLOBALS['TYPO3_DB']->sql_query('SET @uid = ' . $page['uid']); $res2 = $GLOBALS['TYPO3_DB']->sql_query(' EXECUTE contentStmt USING @uid; '); while($data = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res2)) { $content .= '<li>' . $data['header'] . '</li>'; } $content .= '</ul>'; } $GLOBALS['TYPO3_DB']->sql_query('DEALLOCATE PREPARE contentStmt'); |
So werden irgendwann mal die von TYPO3 verarbeiteten Abfragen aussehen. Ich habe diese drei Abfragen mal in einer Extension zusammengeschnürt: Test Prepared Statements. Evtl. werdet Ihr bemerken, dass die PREPARED-Queries bei wenigen Durchläufen nicht wirklich Speed bringen. Jeder SQL-Befehl, der zum Server gesendet werden muss braucht Zeit. Einmal für die PREPARED-Query und einmal für den eigentlichen SELECT-Query. Verwendet also bitte nicht für jeden SELECT die PREPARED-Statements. Setzt Sie bitte nur dann ein, wenn Ihr mehrere ähnliche SELECTs absetzen müsst. Erst dann entfaltet sich die wahre Performance. Bei einer Indexierung von über 50.000 Datensätzen konnte ich über 30% Geschwindigkeit rausholen.