Im Rahmen von SQL-Tuning-Maßnahmen sieht man zuweilen, dass Subqueries zur Ermittlung von Spaltenwerten direkt in der SELECT-Klausel verwendet werden. Obwohl syntaktisch korrekt, ist das regelmäßig ein Stolperstein für den Optimizer. Er kommt offenkundig mit der Abschätzung “echter” Joins besser zurecht.
Da der Rückgabewert der Subquery nur ein einzeiliger Skalar oder NULL sein kann (da andernfalls die Query fehlschlagen würde), erreicht man mit Umstellung auf LEFT OUTER JOIN das selbe Ergebnis. Aus dem genannten Grund sind weder mehr als eine Zeile auf der “rechten” Seite des Joins zu erwarten noch besteht die Gefahr der Eliminierung von Ergebniszeilen der “linken” Seite durch den Join, da es sich ja um einen OUTER Join handelt.
SELECT first_name, last_name,
(SELECT department_name
FROM departments
WHERE department_id = e.department_id)
FROM employees e;
-------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 107 | 1926 | 14 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| DEPARTMENTS | 1 | 16 | 1 (0)| 00:00:01 |
|* 2 | INDEX UNIQUE SCAN | DEPT_ID_PK | 1 | | 0 (0)| 00:00:01 |
| 3 | TABLE ACCESS FULL | EMPLOYEES | 107 | 1926 | 3 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------
Die ursprüngliche Query wird ähnlich einem NESTED LOOP ausgeführt. Für jede der 107 Zeilen aus EMPLOYEES wird über je einen INDEX UNIQUE SCAN der Indexeintrag des zugehörigen Departments gesucht und über je einen weiteren ROWID-Zugriff der department_name aus der DEPARTMENTS-Tabelle ermittelt. Das kann bei ausreichend großer “äußerer” Tabelle ein sehr unperformanter Zugriffspfad sein. NESTED LOOPs performen nur bei geringer Kardinalität der “äußeren” Tabelle gut.
SELECT first_name, last_name, department_name
FROM employees e
LEFT JOIN departments d using (department_id);
----------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 107 | 3638 | 6 (0)| 00:00:01 |
|* 1 | HASH JOIN OUTER | | 107 | 3638 | 6 (0)| 00:00:01 |
| 2 | TABLE ACCESS FULL| EMPLOYEES | 107 | 1926 | 3 (0)| 00:00:01 |
| 3 | TABLE ACCESS FULL| DEPARTMENTS | 27 | 432 | 3 (0)| 00:00:01 |
----------------------------------------------------------------------------------
Der LEFT OUTER JOIN hingegen wird über einen (outer) HASH JOIN abgewickelt, der insbesondere bei großer “äußerer” Tabelle viel besser performt, als ein NESTED LOOP. Das spiegelt sich im angegebenen Beispiel vor allem in der Cost-Schätzung wider.
Share this article