Inhaltsverzeichnis
Im vorliegenden Kapitel werden Beschränkungen beschrieben, die für MySQL-Features wie beispielsweise Unterabfragen oder Views gelten.
Einige hier aufgeführten Beschränkungen gelten für alle gespeicherten Routinen, also sowohl für gespeicherte Prozeduren als auch für gespeicherte Funktionen. Manche Beschränkungen gelten nur für gespeicherte Funktionen, aber nicht für gespeicherte Prozeduren.
Alle Beschränkungen für gespeicherte Funktionen gelten auch für Trigger. Außerdem sind Trigger zurzeit noch nicht für Fremdschlüsselaktionen aktiviert.
Gespeicherte Routinen dürfen keine beliebigen SQL-Anweisungen enthalten. Die folgenden Anweisungen sind unzulässig:
die Sperranweisungen LOCK TABLES
und
UNLOCK TABLES
LOAD DATA
und LOAD TABLE
Vorbereitete SQL-Anweisungen (PREPARE
,
EXECUTE
, DEALLOCATE
PREPARE
). Folge: Sie können in gespeicherten
Routinen kein dynamisches SQL benutzen (indem Sie Anweisungen
dynamisch als Strings generieren und dann ausführen). Diese
Einschränkung wird in MySQL 5.0.13 für gespeicherte
Prozeduren aufgehoben, gilt aber weiterhin für gespeicherte
Funktionen und Trigger.
Für gespeicherte Funktionen (nicht aber gespeicherte Prozeduren) sind zusätzlich folgende Anweisungen unzulässig:
Anweisungen, die ein explizites oder implizites Commit oder Rollback ausführen
Anweisungen, die eine Ergebnismenge zurückgeben. Dazu
gehören auch SELECT
-Anweisungen, die keine
INTO
-Klausel
besitzen, sowie var_list
SHOW
-Anweisungen. Eine
Funktion kann eine Ergebnismenge entweder mit SELECT
... INTO
oder
mit einem Cursor und var_list
FETCH
-Anweisungen
verarbeiten. Siehe Abschnitt 19.2.7.3, „SELECT ... INTO
-Anweisung“.
FLUSH
-Anweisungen
Rekursive Anweisungen. Das bedeutet, dass gespeicherte Funktionen nicht rekursiv benutzt werden können.
Achtung: Obwohl einige Beschränkungen normalerweise zwar für
gespeicherte Funktionen und Trigger, aber nicht für gespeicherte
Prozeduren gelten sollten, gelten sie dennoch für gespeicherte
Prozeduren, wenn diese aus einer gespeicherten Funktion oder einem
Trigger heraus aufgerufen werden. So können Sie zwar
FLUSH
in einer gespeicherten Prozedur
verwenden, aber keine derartige gespeicherte Prozedur aus einer
gespeicherten Funktion oder einem Trigger heraus aufrufen.
Es ist möglich, denselben Bezeichner für eine Routine, einen Parameter, eine lokale Variable und eine Tabellenspalte zu benutzen. Zudem kann derselbe lokale Variablenname in geschachtelten Blöcken eingesetzt werden. Zum Beispiel:
CREATE PROCEDURE p (i INT) BEGIN DECLARE i INT DEFAULT 0; SELECT i FROM t; BEGIN DECLARE i INT DEFAULT 1; SELECT i FROM t; END; END;
In solchen Fällen ist der Bezeichner nicht mehr eindeutig, sodass folgende Präzedenzregeln gelten:
Eine lokale Variable hat Vorrang vor einem Routinenparameter oder einer Tabellenspalte.
Ein Routinenparameter hat Vorrang vor einer Tabellenspalte.
Eine lokale Variable in einem inneren Block hat Vorrang vor einer lokalen Variablen in einem äußeren Block.
Dass Tabellenspalten nicht Vorrang vor Variablen haben, ist ein nichtstandardmäßiges Verhalten.
Die Verwendung von gespeicherten Routinen kann Replikationsprobleme verursachen. Dieses Thema wird in Abschnitt 19.4, „Binärloggen gespeicherter Routinen und Trigger“, eingehender behandelt.
Da INFORMATION_SCHEMA
noch keine
PARAMETERS
-Tabelle hat, müssen Anwendungen,
die Daten von Routinenparametern zur Laufzeit benötigen,
Workarounds einsetzen, also beispielsweise die Ausgabe der
SHOW CREATE
-Anweisungen parsen.
Es gibt keine Debuggingfähigkeiten für gespeicherte Routinen.
CALL
-Anweisungen dürfen keine vorbereiteten
Anweisungen sein. Das gilt sowohl für serverseitige vorbereitete
Anweisungen als auch für vorbereitete SQL-Anweisungen.
UNDO
-Handler werden nicht unterstützt.
FOR
-Schleifen werden nicht unterstützt.
Um Probleme mit der Interaktion zwischen Server-Threads zu verhindern, verwendet der Server, wenn ein Client eine Anweisung gibt, einen Schnappschuss der Routinen und Trigger, die zur Ausführung dieser Anweisung verfügbar sind. Das bedeutet, dass der Server eine Liste von Prozeduren, Funktionen und Triggern berechnet, die bei der Ausführung der Anweisung eingesetzt werden dürfen, diese lädt und dann zur Ausführung der Anweisung schreitet. Die Folge: Während der Ausführung erkennt der Server keine Änderungen an Routinen, die von anderen Threads vorgenommen werden.
Cursors auf der Serverseite werden in der C-API mit der Funktion
mysql_stmt_attr_set()
implementiert. Dieselbe
Implementierung wird auch für Cursors in gespeicherten Routinen
verwendet. Mit einem Cursor auf der Serverseite kann dort eine
Ergebnismenge generiert werden, wobei an den Client allerdings nur
diejenigen Zeilen übermittelt werden, die dieser verlangt. Wenn
ein Client beispielsweise eine Anfrage ausführt, sich aber nur
für die erste Zeile interessiert, werden die restlichen Zeilen
nicht übertragen.
In MySQL wird ein serverseitiger Cursor von einer temporären
Tabelle verkörpert (materialisiert). Diese ist zu Beginn eine
MEMORY
-Tabelle, wird aber dann in eine
MyISAM
-Tabelle umgewandelt, wenn ihre Größe
den Wert der Systemvariablen
max_heap_table_size
erreicht. Diese
Implementierung führt unter anderem dazu, dass der Abruf von
Zeilen mit einem Cursor bei großen Ergebnismengen langsam ist.
Cursors sind nur-lesend; mit einem Cursor können Sie also keine Zeilen aktualisieren.
UPDATE WHERE CURRENT OF
und DELETE
WHERE CURRENT OF
sind nicht implementiert, da änderbare
Cursors nicht unterstützt werden.
Cursors lassen sich nicht nach einem Commit offen halten.
Cursors unterscheiden nicht zwischen Groß- und Kleinschreibung.
Cursors sind nicht scrollbar.
Cursors haben keinen Namen. Der Anweisungs-Handler fungiert als Cursor-ID.
Sie können immer nur einen einzigen Cursor pro vorbereiteter Anweisung offen haben. Wenn Sie mehrere Cursors benötigen, müssen Sie mehrere Anweisungen vorbereiten.
Sie dürfen keinen Cursor für eine Anweisung benutzen, die eine
Ergebnismenge generiert, wenn die Anweisung nicht im
Prepared-Modus unterstützt wird. Dazu gehören Anweisungen wie
beispielsweise CHECK TABLES
, HANDLER
READ
und SHOW BINLOG EVENTS
.
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.
Die View-Verarbeitung ist nicht optimiert:
Es ist nicht möglich, auf einer View einen Index anzulegen.
Indizes können für Views eingesetzt werden, die mit dem Merge-Algorithmus verarbeitet werden. Wird jedoch eine View mit dem Temptable-Algorithmus verarbeitet, kann sie nicht von den Indizes ihrer zugrunde liegenden Tabellen profitieren (obwohl Indizes bei der Erzeugung der temporären Tabellen eingesetzt werden können).
Unterabfragen dürfen nicht in der FROM
-Klausel
einer View verwendet werden. Diese Beschränkung wird jedoch in
Zukunft aufgehoben.
Generell können Sie nicht in derselben Unterabfrage eine Tabelle modifizieren und gleichzeitig mit Select abfragen. Siehe Abschnitt I.3, „Beschränkungen von Unterabfragen“.
Dasselbe Prinzip gilt auch, wenn Sie eine View abfragen, die ihrerseits eine Tabelle abfragt, wenn die View die Tabelle in einer Unterabfrage abfragt und mit dem Merge-Algorithmus ausgewertet wird. Beispiel:
CREATE VIEW v1 AS SELECT * FROM t2 WHERE EXISTS (SELECT 1 FROM t1 WHERE t1.a = t2.a); UPDATE t1, v2 SET t1.a = 1 WHERE t1.b = v2.b;
Wird die View mit einer temporären Tabelle ausgewertet,
können Sie diese Tabelle in der
View-Unterabfrage abfragen und in der äußeren Abfrage dennoch
die Tabelle modifizieren. In diesem Fall wird die View nämlich
materialisiert, sodass Sie die Tabelle in Wirklichkeit gar nicht
„zur selben Zeit“ in einer Unterabfrage abfragen und
modifizieren. (Dies ist ein weiterer Grund, MySQL zur Verwendung
des Temptable-Algorithmus zu zwingen, indem Sie ALGORITHM
= TEMPTABLE
in der View-Definition angeben.)
Mit DROP TABLE
oder ALTER
TABLE
können Sie eine Tabelle löschen oder ändern,
die in einer View-Definition benutzt wird (wodurch die View
ungültig wird). Die Löschungs- oder Änderungsoperation löst
keine Warnung aus. Erst später, wenn die View benutzt wird, wird
ein Fehler gemeldet.
Eine View-Definition wird von bestimmten Anweisungen „eingefroren“:
Wenn eine von PREPARE
vorbereitete
Anweisung auf eine View verweist, spiegeln die Inhalte der
View jedes Mal, wenn die Anweisung im weiteren Verlauf
ausgeführt wird, den Zustand zu dem Zeitpunkt wider, da sie
vorbereitet wurde. Das gilt auch dann, wenn die
View-Definition zwischen der Vorbereitung und der Ausführung
der Anweisung geändert wurde. Beispiel:
CREATE VIEW v AS SELECT 1; PREPARE s FROM 'SELECT * FROM v'; ALTER VIEW v AS SELECT 2; EXECUTE s;
Die EXECUTE
-Anweisung gibt 1 und nicht 2
zurück.
Wenn eine Anweisung in einer gespeicherten Routine eine View benutzt, dann mit dem Inhalt, den die View bei der ersten Ausführung der Anweisung hatte. Das bedeutet beispielsweise: Wenn die Anweisung in einer Schleife ausgeführt wird, bekommt sie in allen weiteren Durchläufen immer denselben View-Inhalt zu sehen, selbst wenn die View-Definition später in der Schleife geändert wird. Beispiel:
CREATE VIEW v AS SELECT 1; delimiter // CREATE PROCEDURE p () BEGIN DECLARE i INT DEFAULT 0; WHILE i < 5 DO SELECT * FROM v; SET i = i + 1; ALTER VIEW v AS SELECT 2; END WHILE; END; // delimiter ; CALL p();
Wenn die Prozedur p()
aufgerufen wird, gibt
SELECT
bei jedem Schleifendurchlauf 1
zurück, obwohl die View-Definition in der Schleife geändert
wurde.
Im Hinblick auf die Aktualisierungsmöglichkeit besteht für Views
das übergeordnete Ziel, dass jede View, die theoretisch
aktualisiert werden kann, auch in der Praxis aktualisierbar sein
sollte. Dazu gehören auch Views, die in ihrer Definition eine
UNION
haben. Zurzeit sind nicht alle
theoretisch aktualisierbaren Views auch in der Praxis
aktualisierbar. Die ursprüngliche Implementierung von Views war
absichtlich so geschrieben worden, um möglichst schnell
benutzbare und aktualisierbare Views in MySQL hinzuzubekommen.
Viele theoretisch aktualisierbare Views sind dies auch in der
Praxis, aber es gibt immer noch einige Einschränkungen:
Aktualisierbare Views, die irgendwo anders als in der
WHERE
-Klausel Unterabfragen haben. Manche
Views mit Unterabfragen in der SELECT
-Liste
sind möglicherweise aktualisierbar.
Sie können mit UPDATE
nicht mehr als eine
unterliegende Tabelle einer als Join definierten View
aktualisieren.
Sie können eine als Join definierte View nicht mit
DELETE
aktualisieren.
Die XA-Transaktionsunterstützung beschränkt sich auf die
Speicher-Engine InnoDB
.
Die XA-Implementierung von MySQL ist für „externes
XA“ ausgelegt, bei dem ein MySQL Server als
Ressourcenmanager (RMs) und Clientprogramme als
Transaktionsmanager (TMs) fungieren. „Internes XA“
ist nicht implementiert. Dies würde einzelnen Speicher-Engines in
einem MySQL Server erlauben, als RMs zu agieren, und dem Server
selbst, als TM zu fungieren. Internes XA ist für den Umgang mit
XA-Transaktionen erforderlich, an denen mehr als eine
Speicher-Engine beteiligt ist. Die Implementierung von internem XA
ist unvollständig, da sie erfordert, dass eine Speicher-Engine
zweiphasiges Commit auf der Tabellenhandler-Ebene unterstützt,
was zurzeit nur InnoDB
kann.
Für XA START
werden keine
JOIN
- und RESUME
-Klauseln
unterstützt.
Für XA END
wird die SUSPEND [FOR
MIGRATE]
-Klausel nicht unterstützt.
Die Anforderung, dass der bqual
-Teil
des xid
-Werts für jede XA-Transaktion
innerhalb einer globalen Transaktion anders sein muss, ist eine
Beschränkung der derzeitigen XA-Implementierung von MySQL. Sie
ist nicht Teil der XA-Spezifikation.
Wenn eine XA-Transaktion den PREPARED
-Zustand
erreicht hat und der MySQL Server abstürzt, kann die Transaktion
nach dem erneuten Hochfahren des Servers weiterlaufen. Das soll
auch so sein. Wenn allerdings die Clientverbindung abbricht und
der Server weiterläuft, rollt der Server alle anhängigen
XA-Transaktionen zurück, selbst solche, die den
PREPARED
-Zustand erreicht haben. Es sollte
möglich sein, eine PREPARED
-XA-Transaktion zu
committen oder zurückzurollen, doch dies lässt sich nur durch
Änderungen am Mechanismus des Binärlogs erreichen.
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.