Ein bekannter Bug, der noch behoben wird: Wenn Sie einen
NULL
-Wert mithilfe von ALL
,
ANY
oder SOME
mit einer
Unterabfrage vergleichen und die Unterabfrage eine Ergebnismenge
zurückgibt, kann der Vergleich das nichtstandardmäßige Ergebnis
NULL
anstatt TRUE
oder
FALSE
zurückliefern.
Die äußere Anweisung einer Unterabfrage kann ein
SELECT
, INSERT
,
UPDATE
, DELETE
,
SET
oder DO
sein.
Im Allgemeinen können Sie eine Tabelle nicht in einer Unterabfrage modifizieren und zugleich mit einem Select abfragen. Diese Beschränkung gilt beispielsweise für Anweisungen der folgenden Form:
DELETE FROM t WHERE ... (SELECT ... FROM t ...); UPDATE t ... WHERE col = (SELECT ... FROM t ...); {INSERT|REPLACE} INTO t (SELECT ... FROM t ...);
Ausnahme: Das obige Verbot gilt nicht, wenn Sie eine Unterabfrage
für die modifizierte Tabelle in der
FROM
-Klausel verwenden. Beispiel:
UPDATE t ... WHERE col = (SELECT (SELECT ... FROM t...) AS _t ...);
Das Verbot ist hier deshalb außer Kraft gesetzt, weil die
Unterabfrage in der FROM
-Klausel von einer
temporären Tabelle verkörpert wird, sodass die relevanten Zeilen
in t
zu dem Zeitpunkt, da das Update in
t
stattfindet, von dem Select bereits abgerufen
worden sind.
Zeilenvergleichsoperationen werden nur teilweise unterstützt:
Für
kann
expr
IN
(subquery
)expr
ein
n
-Tupel sein (das mit
Zeilenkonstruktorsyntax angegeben ist) und die Unterabfrage
kann Zeilen von n
-Tupeln
zurückgeben.
Für
muss
expr
op
{ALL|ANY|SOME}
(subquery
)expr
ein Skalarwert und die
Unterabfrage eine Spaltenunterabfrage sein. Sie darf keine
Zeilen mit mehreren Spalten zurückgeben.
Mit anderen Worten, für eine Unterabfrage, die Zeilen von
n
--Tupeln zurückgibt, wird Folgendes
unterstützt:
(val_1
, ...,val_n
) IN (subquery
)
Doch dieses wird nicht unterstützt:
(val_1
, ...,val_n
)op
{ALL|ANY|SOME} (subquery
)
Der Grund, weshalb Zeilenvergleiche für IN
unterstützt werden, aber für die anderen Klauseln nicht, besteht
darin, dass IN
aufgrund seiner Implementierung
den Vergleich als eine Serie von =
-Vergleichen
und AND
-Operationen neu schreibt. Dieser Ansatz
kann für ALL
, ANY
oder
SOME
nicht verwendet werden.
Zeilenkonstruktoren sind nicht gut optimiert. Die folgenden beiden Ausdrücke sind äquivalent, aber nur der zweite kann optimiert werden:
(col1, col2, ...) = (val1, val2, ...) col1 = val1 AND col2 = val2 AND ...
Die Unterabfragenoptimierung ist für IN
weniger effizient als für den =
-Operator.
Ein typisches Beispiel für die schlechte Performance von
IN
tritt ein, wenn die Unterabfrage nur wenige,
die übergeordnete Abfrage hingegen viele Zeilen zurückgibt, die
mit den Ergebnissen der Unterabfrage verglichen werden müssen.
Unterabfragen in der FROM
-Klausel dürfen keine
korrelierten Unterabfragen sein. Sie werden materialisiert
(ausgeführt, um eine Ergebnismenge zu erstellen), bevor die
äußere Abfrage ausgeführt wird. Daher können sie nicht pro
Zeile der äußeren Abfrage ausgewertet werden.
Da der Optimierer für Joins besser als für Unterabfragen gerüstet ist, lassen sich oft Anweisungen mit Unterabfragen effizienter ausführen, wenn man sie als Joins umformuliert.
Eine Ausnahme bildet der Fall, in dem eine
IN
-Unterabfrage als SELECT
DISTINCT
-Join umformuliert werden kann. Beispiel:
SELECT col FROM t1 WHERE id_col IN (SELECT id_col2 FROM t2 WHERE condition
);
Diese Anweisung kann man folgendermaßen umformulieren:
SELECT DISTINCT col FROM t1, t2 WHERE t1.id_col = t2.id_col AND condition
;
Doch hier erfordert der Join eine zusätzliche
DISTINCT
-Operation und ist deswegen nicht
effizienter als die Unterabfrage.
Eine mögliche zukünftige Optimierung könnte darin bestehen, dass MySQL die Join-Reihenfolge nicht für die Auswertung der Unterabfrage neu schreibt. In manchen Fällen ließe sich eine Unterabfrage effizienter ausführen, wenn MySQL sie als Join umformulierte. Dies gäbe dem Optimierer die Gelegenheit, zwischen mehreren Ausführungsplänen auszuwählen. Er könnte beispielsweise entscheiden, welche von zwei Tabellen er als Erste liest.
Beispiel:
SELECT a FROM outer_table AS ot WHERE a IN (SELECT a FROM inner_table AS it WHERE ot.b = it.b);
Für diese Anfrage würde MySQL immer zuerst die
outer_table
scannen und dann die Unterabfrage
für jede Zeile auf der inner_table
ausführen.
Wenn outer_table
viele und
inner_table
wenige Zeilen hat, liefe die
Anfrage nicht so schnell, wie sie könnte.
Die obige Anfrage könnte man folgendermaßen umformulieren:
SELECT a FROM outer_table AS ot, inner_table AS it WHERE ot.a = it.a AND ot.b = it.b;
In diesem Fall scannen wir zuerst die kleine Tabelle
(inner_table
) und schauen danach die Zeilen in
der großen outer_table
nach. Das geht ganz
schnell, wenn wir einen Index auf (ot.a,ot.b)
haben.
Mögliche zukünftige Optimierung: Eine korrelierte Unterabfrage wird für jede Zeile der äußeren Abfrage ausgewertet. Es wäre besser, die Unterabfrage nicht erneut auszuwerten, wenn die Werte der äußeren Zeile immer noch dieselben sind wie in der vorherigen Zeile. Stattdessen könnte man das vorherige Ergebnis wiederverwenden.
Mögliche zukünftige Optimierung: Eine Unterabfrage in der
FROM
-Klausel wird ausgewertet, indem ihr
Ergebnis in einer temporären Tabelle festgehalten wird, die keine
Indizes verwendet. So können auch bei Vergleichen mit anderen
Tabellen in der Abfrage keine Indizes eingesetzt werden, auch dann
nicht, wenn es sinnvoll wäre.
Mögliche zukünftige Optimierung: Wenn eine Unterabfrage in der
FROM
-Klausel einer View ähnelt, auf die der
Merge-Algorithmus angewendet werden kann, formulieren Sie die
Anfrage um und wenden den Merge-Algorithmus an, damit Indizes
genutzt werden können. Die folgende Anweisung enthält eine
solche Unterabfrage:
SELECT * FROM (SELECT * FROM t1 WHERE t1.t1_col) AS _t1, t2 WHERE t2.t2_col;
Die Anweisung kann folgendermaßen als Join geschrieben werden:
SELECT * FROM t1, t2 WHERE t1.t1_col AND t2.t2_col;
Diese Umformulierung hat zwei Vorteile:
Sie verhindert den Einsatz einer temporären Tabelle, für die
keine Indizes genutzt werden können. In der umformulierten
Version kann der Optimierer Indizes auf t1
verwenden.
Sie gibt dem Optimierer mehr Freiheiten, zwischen
verschiedenen Ausführungsplänen zu wählen. Indem er sie als
Join umformuliert, kann der Optimierer beispielsweise
entscheiden, ob er t1
oder
t2
zuerst benutzt.
Mögliche zukünftige Optimierung: Für IN
,
= ANY
, <> ANY
,
= ALL
und <> ALL
mit
nichtkorrelierten Unterabfragen könnte man für das Ergebnis
einen speicherresidenten Hash oder, bei größeren Ergebnismengen,
eine temporäre Tabelle mit Index verwenden. Beispiel:
SELECT a FROM big_table AS bt WHERE non_key_field IN (SELECT non_key_field FROMtable
WHEREcondition
)
In diesem Fall würden wir eine temporäre Tabelle anlegen:
CREATE TABLE t (key (non_key_field)) (SELECT non_key_field FROMtable
WHEREcondition
)
Dann könnten wir für jede Zeile in big_table
einen Schlüssel-Lookup in t
anhand von
bt.non_key_field
machen.
Dies ist eine Übersetzung des MySQL-Referenzhandbuchs, das sich auf dev.mysql.com befindet. Das ursprüngliche Referenzhandbuch ist auf Englisch, und diese Übersetzung ist nicht notwendigerweise so aktuell wie die englische Ausgabe. Das vorliegende deutschsprachige Handbuch behandelt MySQL bis zur Version 5.1.