Anhang I. Feature-Beschränkungen

Inhaltsverzeichnis

I.1. Beschränkungen bei gespeicherten Routinen und Triggern
I.2. Beschränkungen von serverseitigen Cursorn
I.3. Beschränkungen von Unterabfragen
I.4. Beschränkungen bei Views
I.5. Beschränkungen bei XA-Transaktionen

Im vorliegenden Kapitel werden Beschränkungen beschrieben, die für MySQL-Features wie beispielsweise Unterabfragen oder Views gelten.

I.1. Beschränkungen bei gespeicherten Routinen und Triggern

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 var_list-Klausel besitzen, sowie SHOW-Anweisungen. Eine Funktion kann eine Ergebnismenge entweder mit SELECT ... INTO var_list oder mit einem Cursor und 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.

I.2. Beschränkungen von serverseitigen Cursorn

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.

I.3. Beschränkungen von Unterabfragen

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 expr IN (subquery) kann expr ein n-Tupel sein (das mit Zeilenkonstruktorsyntax angegeben ist) und die Unterabfrage kann Zeilen von n-Tupeln zurückgeben.

  • Für expr op {ALL|ANY|SOME} (subquery) muss 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 FROM table WHERE condition)

In diesem Fall würden wir eine temporäre Tabelle anlegen:

CREATE TABLE t (key (non_key_field))
(SELECT non_key_field FROM table WHERE condition)

Dann könnten wir für jede Zeile in big_table einen Schlüssel-Lookup in t anhand von bt.non_key_field machen.

I.4. Beschränkungen bei Views

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.

I.5. Beschränkungen bei XA-Transaktionen

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.