Kapitel 19. Gespeicherte Prozeduren und Funktionen

Inhaltsverzeichnis

19.1. Gespeicherte Routinen und die Berechtigungstabellen
19.2. Syntax gespeicherter Prozeduren
19.2.1. CREATE PROCEDURE und CREATE FUNCTION
19.2.2. ALTER PROCEDURE und ALTER FUNCTION
19.2.3. DROP PROCEDURE und DROP FUNCTION
19.2.4. Syntax der CALL-Anweisung
19.2.5. BEGIN ... END-Syntax für komplexe Anweisungen
19.2.6. Syntax der DECLARE-Anweisung
19.2.7. Variablen in gespeicherten Routinen
19.2.8. Bedingungen und Handler
19.2.9. Cursor
19.2.10. Konstrukte für die Ablaufsteuerung
19.3. Gespeicherte Prozeduren, Funktionen, Trigger und Replikation: häufig gestellte Fragen
19.4. Binärloggen gespeicherter Routinen und Trigger

MySQL 5.1 kennt auch gespeicherte Routinen (Prozeduren und Funktionen). Eine gespeicherte Prozedur ist eine Menge von SQL-Anweisungen, die auf dem Server gespeichert werden kann. So müssen Clients nicht immer wieder die jeweiligen Einzelanweisungen ausführen, sondern können stattdessen die gespeicherte Prozedur aufrufen.

In folgenden Situationen sind gespeicherte Routinen besonders nützlich:

Gespeicherte Routinen bieten eine bessere Leistung, da weniger Informationen zwischen Server und Client übermittelt werden müssen. Der Nachteil ist der, dass die Belastung des Datenbankservers steigt, weil mehr Arbeit auf der Serverseite und weniger Arbeit auf der Seite des Clients (der Anwendungen) erledigt werden muss. Dies müssen Sie berücksichtigen, wenn viele Clientcomputer (wie beispielsweise Webserver) von nur einem oder sehr wenigen Datenbankservern bedient werden.

Mit gespeicherten Routinen sind Funktionsbibliotheken im Datenbankserver möglich. Dieses Feature haben auch moderne Anwendungssprachen, die einen solchen Entwurf intern umsetzen (zum Beispiel durch Klassen). Auch jenseits von Datenbankanwendungen bringen diese Features der Anwendungssprachen dem Programmierer Vorteile.

MySQL verwendet die Syntax für gespeicherte Routinen gemäß dem SQL:2003-Standard, den auch DB2 von IBM nutzt.

Die Implementierung gespeicherter Routinen in MySQL ist noch nicht abgeschlossen. Unterstützt wird allein die in diesem Kapitel beschriebene Syntax. Beschränkungen und Erweiterungen werden an geeigneter Stelle geschildert. Eine weitergehende Behandlung der Restriktionen, die für die Verwendung gespeicherter Routinen gelten, finden Sie in Abschnitt I.1, „Beschränkungen bei gespeicherten Routinen und Triggern“.

Das Binärlogging für gespeicherte Routinen wird in Abschnitt 19.4, „Binärloggen gespeicherter Routinen und Trigger“ beschrieben.

19.1. Gespeicherte Routinen und die Berechtigungstabellen

Für gespeicherte Routinen muss die Tabelle proc in der mysql-Datenbank vorhanden sein. Diese Tabelle wird schon bei der Installationsprozedur von MySQL 5.1 angelegt. Wenn Sie von einer älteren Version auf MySQL 5.1 aufrüsten, achten Sie bitte darauf, Ihre Berechtigungstabellen zu aktualisieren, damit die Tabelle proc vorhanden ist. Siehe auch Abschnitt 5.6, „mysql_fix_privilege_tables — Upgrade von MySQL-Systemtabellen“.

Nach Anweisungen, die gespeicherte Routinen erzeugen, ändern oder löschen, bearbeitet der Server die Tabelle mysql.proc. Eine manuelle Manipulation dieser Tabelle wird der Server nicht bemerken.

Im Berechtigungssystem von MySQL werden gespeicherte Routinen folgendermaßen behandelt:

  • Das CREATE ROUTINE-Recht ist erforderlich, um gespeicherte Routinen zu erzeugen.

  • Das ALTER ROUTINE-Recht wird benötigt, um gespeicherte Routinen zu ändern oder zu löschen. Dieses Recht wird automatisch dem Erzeuger einer Routine erteilt.

  • Das EXECUTE-Recht ist notwendig, um gespeicherte Routinen auszuführen, wird aber ebenfalls dem Erzeuger einer Routine automatisch erteilt. Das voreingestellte SQL SECURITY-Merkmal einer Routine ist DEFINER und erlaubt es Benutzern, mit denen die Routine verbunden ist, und die darüber hinaus Datenbankzugriff haben, die Routine auszuführen.

19.2. Syntax gespeicherter Prozeduren

Eine gespeicherte Routine ist entweder eine Prozedur oder eine Funktion. Gespeicherte Routinen werden mit den Anweisungen CREATE PROCEDURE und CREATE FUNCTION angelegt. Eine Prozedur wird mit einer CALL-Anweisung aufgerufen und kann nur über Ausgabevariablen Werte zurückgeben. Eine Funktion kann innerhalb einer Anweisung aufgerufen werden - wie jede andere Funktion auch (also durch Nennung ihres Namens) - und einen Skalarwert zurückliefern. Gespeicherte Routinen können andere gespeicherte Routinen aufrufen.

Eine gespeicherte Prozedur oder Funktion steht mit einer bestimmten Datenbank in Zusammenhang. Das hat Folgen:

  • Wenn die Routine aufgerufen wird, wird implizit ein USE db_name ausgeführt (und wieder rückgängig gemacht, wenn die Routine endet). USE-Anweisungen sind in gespeicherten Routinen nicht zulässig.

  • Den Namen einer Routine können Sie mit einem Datenbanknamen qualifizieren, etwa wenn Sie eine Routine benutzen möchten, die nicht in der aktuellen Datenbank vorliegt. Um beispielsweise eine Prozedur p oder eine Funktion f aus der Datenbank test aufzurufen, können Sie CALL test.p() oder test.f() sagen.

  • Wird eine Datenbank gelöscht, so werden alle ihre gespeicherten Routinen mitgelöscht.

MySQL unterstützt die praktischen Erweiterungen, die eine Nutzung der regulären SELECT-Anweisungen innerhalb von gespeicherten Prozeduren ermöglichen (also ohne Cursors oder lokale Variablen). Die Ergebnismenge einer solchen Anfrage wird einfach direkt an den Client gesandt. Da mehrere SELECT-Anweisungen mehrere Ergebnismengen generieren, muss der Client eine MySQL-Clientbibliothek verwenden, die mehrere Ergebnismengen unterstützt. Das bedeutet, dass er die Clientbibliothek einer MySQL-Version benötigt, die mindestens so jung ist wie 4.1. Außerdem sollte der Client die Option CLIENT_MULTI_STATEMENTS angeben, wenn er sich verbindet. C-Programme können dies mit der C-API-Funktion mysql_real_connect() tun (siehe Abschnitt 24.2.3.51, „mysql_real_connect()).

Die folgenden Abschnitte beschreiben die Syntax, mit der gespeicherte Prozeduren und Funktionen erzeugt, geändert und aufgerufen werden.

19.2.1. CREATE PROCEDURE und CREATE FUNCTION

CREATE PROCEDURE sp_name ([proc_parameter[,...]])
    [characteristic ...] routine_body

CREATE FUNCTION sp_name ([func_parameter[,...]])
    RETURNS type
    [characteristic ...] routine_body
    
proc_parameter:
    [ IN | OUT | INOUT ] param_name type
    
func_parameter:
    param_name type

type:
    Any valid MySQL data type

characteristic:
    LANGUAGE SQL
  | [NOT] DETERMINISTIC
  | { CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA }
  | SQL SECURITY { DEFINER | INVOKER }
  | COMMENT 'string'

routine_body:
    Valid SQL procedure statement

Diese Anweisungen erzeugen gespeicherte Routinen. Um sie nutzen zu können, ist das CREATE ROUTINE-Recht erforderlich. Wenn Binärlogging eingeschaltet ist, kann für die CREATE FUNCTION-Anweisung auch das SUPER-Recht erforderlich sein, wie in Abschnitt 19.4, „Binärloggen gespeicherter Routinen und Trigger“, beschrieben. MySQL erteilt dem Erzeuger einer Routine automatisch das ALTER ROUTINE- und das EXECUTE-Recht.

Nach Voreinstellung wird die Routine mit der Standarddatenbank verbunden. Um sie explizit mit einer bestimmten Datenbank zu verbinden, geben Sie bei der Erzeugung der Routine den Namen der Datenbank in der Form db_name.sp_name an.

Wenn der Name der Routine gleichlautend mit dem Namen einer eingebauten SQL-Funktion ist, müssen Sie zwischen dem Namen und der nachfolgenden Klammer ein Leerzeichen setzen, wenn Sie die Routine definieren, sonst tritt ein Syntaxfehler ein. Das gilt auch, wenn Sie die Routine zu einem späteren Zeitpunkt aufrufen. Aus diesem Grund raten wir Ihnen, keine Namen vorhandener SQL-Funktionen für Ihre eigenen gespeicherten Routinen zu verwenden.

Der SQL-Modus IGNORE_SPACE gilt für eingebaute Funktionen und nicht für gespeicherte Routinen. Es ist immer zulässig, Leerzeichen hinter den Namen einer Routine zu setzen, egal ob IGNORE_SPACE eingeschaltet ist oder nicht.

Die Parameterliste in runden Klammern muss immer vorhanden sein. Wenn keine Parameter übergeben werden, muss eine leere Parameterliste () verwendet werden. Jeder Parameter ist nach Voreinstellung ein IN-Parameter. Um einen Parameter anderslautend zu definieren, setzen Sie das Schlüsselwort OUT oder INOUT vor den Parameternamen.

Hinweis: Als IN, OUT oder INOUT können nur PROCEDURE-Parameter definiert werden. (FUNCTION-Parameter gelten immer als IN-Parameter.)

Jeder Parameter kann mit jedem zulässigen Datentyp deklariert werden, nur das Attribut COLLATE darf nicht verwendet werden.

Die RETURNS-Klausel kann nur für eine FUNCTION angegeben werden; hier ist sie sogar obligatorisch. Sie gibt den Rückgabetyp der Funktion an. Der Funktionsrumpf muss eine RETURN value-Anweisung enthalten.

Der routine_body besteht aus einer gültigen SQL-Prozeduranweisung. Diese kann eine einfache Anweisung wie etwa ein SELECT oder INSERT sein, sie kann aber auch eine zusammengesetzte Anweisung mit BEGIN und END sein. Die Syntax zusammengesetzter Anweisungen wird in Abschnitt 19.2.5, „BEGIN ... END-Syntax für komplexe Anweisungen“, erläutert. Zusammengesetzte Anweisungen können Deklarationen, Schleifen und andere Anweisungen mit Kontrollstrukturen enthalten. Die Syntax dieser Anweisungen wird weiter unten in diesem Kapitel erklärt, beispielsweise unter Abschnitt 19.2.6, „Syntax der DECLARE-Anweisung“, und Abschnitt 19.2.10, „Konstrukte für die Ablaufsteuerung“.

Die CREATE FUNCTION-Anweisung diente in früheren MySQL-Versionen der Unterstützung von UDFs (User-defined Functions, benutzerdefinierte Funktionen). Siehe auch Abschnitt 26.3, „Hinzufügen neuer Funktionen zu MySQL“. UDFs werden weiterhin unterstützt, obwohl inzwischen auch die gespeicherten Funktionen existieren. Eine UDF kann man als externe gespeicherte Funktion betrachten. Bitte beachten Sie jedoch, dass gespeicherte Funktionen denselben Namensraum wie UDFs benutzen.

Eine Prozedur oder Funktion gilt als „deterministisch“, wenn sie für gleiche Eingabeparameter immer gleiche Resultate erzeugt; ansonsten ist sie „nichtdeterministisch“. Wenn in der Definition der Routine weder DETERMINISTIC noch NOT DETERMINISTIC steht, ist die Voreinstellung NOT DETERMINISTIC.

Im Zusammenhang mit Replikation wird eine Routine durch Verwendung der Funktion NOW() (oder ihrer Synonyme) oder RAND() nicht unbedingt nichtdeterministisch. Für NOW() enthält das Binärlog den Zeitstempel und repliziert korrekt. RAND() repliziert ebenfalls richtig, sofern es in einer Routine nur ein einziges Mal aufgerufen wird. (Den Ausführungszeitstempel der Routine und den Zufallszahlen-Seed können Sie als implizite Eingaben betrachten, die auf Master und Slave identisch sind.)

Zurzeit wird das DETERMINISTIC-Merkmal vom Optimierer zwar akzeptiert, aber noch nicht benutzt. Wenn jedoch Binärlogging eingeschaltet ist, nimmt dieses Merkmal Einfluss darauf, welche Routinendefinitionen von MySQL akzeptiert werden. Siehe Abschnitt 19.4, „Binärloggen gespeicherter Routinen und Trigger“.

Einige Merkmale informieren über das Wesen der von der Routine verwendeten Daten. CONTAINS SQL weist darauf hin, dass die Routine keine Anweisungen zum Lesen oder Schreiben von Daten enthält. NO SQL bedeutet, dass sie keine SQL-Anweisungen enthält. READS SQL DATA kennzeichnet Routinen mit lesenden, aber ohne schreibende Anweisungen und MODIFIES SQL DATA Routinen mit Anweisungen, die Daten schreiben können. CONTAINS SQL ist der Standardwert, wenn kein anderes dieser Merkmale explizit angegeben ist. Die Merkmale haben nur beratenden Charakter. Sie schränken den Server nicht ein, wenn es darum geht, welche Arten von Anweisungen ausgeführt werden dürfen oder nicht.

Das Merkmal SQL SECURITY gibt an, ob die Routine mit den Berechtigungen des Benutzers, der sie erzeugte, oder des Benutzers, der sie aufruft, ausgeführt werden soll. Der Standardwert ist DEFINER. Dieses Feature ist neu in SQL:2003. Der Erzeuger oder Aufrufer muss die Zugriffsberechtigung auf die Datenbank haben, mit der die Routine verbunden ist. Um die Routine auszuführen, ist das EXECUTE-Recht erforderlich. Der Benutzer, der diese Berechtigung haben muss, kann nur entweder der Erzeuger oder der Aufrufer sein, je nachdem, wie SQL SECURITY eingestellt ist.

MySQL speichert die Einstellung der Systemvariablen sql_mode, die gerade in Kraft ist, wenn eine Routine erzeugt wird, und führt diese Routine dann immer mit dieser Einstellung aus.

Beim Aufruf der Routine wird ein implizites USE db_name ausgeführt (und wieder rückgängig gemacht, wenn die Routine endet). USE-Anweisungen sind in gespeicherten Routinen nicht erlaubt.

Der Server verwendet den Datentyp eines Routinenparameters oder Funktionsrückgabewerts wie folgt. Diese Regeln gelten auch für lokale Laufzeitvariablen, die mit der DECLARE-Anweisung angelegt wurden (Abschnitt 19.2.7.1, „Lokale DECLARE-Variablen“).

  • Zuweisungen werden auf Datentypinkompatibilitäten und Überlauf überprüft. Konvertierungs- und Überlaufprobleme werden mit Warnungen oder im Strict-Modus sogar mit Fehlern quittiert.

  • Für Zeichendatentypen gilt: Wenn in der Deklaration eine CHARACTER SET-Klausel steht, wird der angegebene Zeichensatz mit seiner Standardkollation verwendet. Fehlt eine solche Klausel, werden der Zeichensatz und die Kollation der Datenbank benutzt. (Diese sind durch die Systemvariablen character_set_database und collation_database vorgegeben.)

  • Parametern oder Variablen können nur Skalarwerte zugewiesen werden. Eine Anweisung wie etwa SET x = (SELECT 1, 2) wäre ungültig.

Die COMMENT-Klausel ist eine MySQL-Erweiterung und kann zur Beschreibung der gespeicherten Routine genutzt werden. Diese Information wird mit den Anweisungen SHOW CREATE PROCEDURE und SHOW CREATE FUNCTION angezeigt.

In MySQL dürfen Routinen auch DDL-Anweisungen wie beispielsweise CREATE und DROP enthalten. Darüber hinaus dürfen gespeicherte Prozeduren (nicht aber gespeicherte Funktionen) in MySQL auch Transaktionsanweisungen in SQL enthalten, wie etwa COMMIT. Gespeicherte Funktionen dürfen keine Anweisungen enthalten, die explizit oder implizit ein Commit oder Rollback ausführen. Eine Unterstützung für diese Anweisungen wird vom SQL-Standard auch gar nicht verlangt, der Standard besagt lediglich, dass jeder DBMS-Hersteller selbst entscheiden kann, ob er sie erlauben will oder nicht.

Gespeicherte Routinen dürfen kein LOAD DATA INFILE enthalten.

Anweisungen, die eine Ergebnismenge zurückgeben, dürfen nicht innerhalb einer gespeicherten Funktion verwendet werden. Dazu gehören auch SELECT-Anweisungen, soweit sie nicht mithilfe einer INTO-Klausel Spaltenwerte in Variablen laden, sowie SHOW und andere Anweisungen wie beispielsweise EXPLAIN. Anweisungen, für die zur Definitionszeit festgelegt werden kann, dass sie eine Ergebnismenge zurückliefern, lösen einen Not allowed to return a result set from a function-Fehler aus (ER_SP_NO_RETSET_IN_FUNC). Anweisungen, für die nur zur Laufzeit festgelegt werden kann, dass sie eine Ergebnismenge zurückliefern, lösen einen PROCEDURE %s can't return a result set in the given context-Fehler aus (ER_SP_BADSELECT).

Das folgende Beispiel zeigt eine einfache gespeicherte Prozedur mit einem OUT-Parameter. Das Beispiel verwendet den mysql-Clientbefehl delimiter, um das Begrenzungszeichen für die Anweisung von ; in // zu ändern, während die Prozedur definiert wird. So kann das Begrenzungszeichen ; im Prozedurrumpf an den Server durchgereicht werden, ohne von mysql selbst interpretiert zu werden.

mysql> delimiter //

mysql> CREATE PROCEDURE simpleproc (OUT param1 INT)
    -> BEGIN
    ->   SELECT COUNT(*) INTO param1 FROM t;
    -> END;
    -> //
Query OK, 0 rows affected (0.00 sec)

mysql> delimiter ;

mysql> CALL simpleproc(@a);
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT @a;
+------+
| @a   |
+------+
| 3    |
+------+
1 row in set (0.00 sec)

Wenn Sie den Befehl delimiter benutzen, geben Sie bitte nicht das Backslash-Zeichen (‘\’) an, da es das Escape-Symbol für MySQL ist.

Das folgende Beispiel zeigt eine Funktion, die einen Parameter entgegennimmt, eine Operation mit einer SQL-Funktion ausführt und das Ergebnis zurückliefert. In diesem Fall ist es nicht nötig, delimiter zu benutzen, da die Funktionsdefinition keine ;-Zeichen als interne Anweisungstrennzeichen enthält:

mysql> CREATE FUNCTION hello (s CHAR(20)) RETURNS CHAR(50)
    -> RETURN CONCAT('Hello, ',s,'!');
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT hello('world');
+----------------+
| hello('world') |
+----------------+
| Hello, world!  |
+----------------+
1 row in set (0.00 sec)

Eine gespeicherte Funktion gibt einen Wert zurück, dessen Datentyp in ihrer RETURNS-Klausel angegeben ist. Wenn die RETURN-Anweisung einen Wert eines anderen Typs zurückgibt, wird dieser mit Gewalt in den richtigen Typ umgewandelt. Wenn eine Funktion beispielsweise einen Rückgabewert vom Typ ENUM oder SET hat, die RETURN-Anweisung jedoch einen Integer zurückgibt, so liefert die Funktion den String für das entsprechende ENUM-Element oder die Menge der SET-Elemente.

19.2.2. ALTER PROCEDURE und ALTER FUNCTION

ALTER {PROCEDURE | FUNCTION} sp_name [characteristic ...]

characteristic:
    { CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA }
  | SQL SECURITY { DEFINER | INVOKER }
  | COMMENT 'string'

Mit dieser Anweisung können die Merkmale einer gespeicherten Prozedur oder Funktion geändert werden. Hierzu benötigen Sie das ALTER ROUTINE-Recht für die betreffende Routine. (Diese Berechtigung wird dem Erzeuger einer Routine automatisch erteilt.) Wenn Binärlogging eingeschaltet ist, kann für die ALTER FUNCTION-Anweisung unter Umständen auch die SUPER-Berechtigung erforderlich werden, wie in Abschnitt 19.4, „Binärloggen gespeicherter Routinen und Trigger“, beschrieben.

In einer einzigen ALTER PROCEDURE- oder ALTER FUNCTION-Anweisung können auch mehrere Änderungen verlangt werden.

19.2.3. DROP PROCEDURE und DROP FUNCTION

DROP {PROCEDURE | FUNCTION} [IF EXISTS] sp_name

Diese Anweisung löscht eine gespeicherte Prozedur oder Funktion in dem Sinne, dass die darin genannte Routine von dem Server entfernt wird. Hierzu benötigen Sie das ALTER ROUTINE-Recht für die betreffende Routine. (Diese Berechtigung wird dem Erzeuger einer Routine automatisch erteilt.)

Die IF EXISTS-Klausel ist eine MySQL-Erweiterung. Sie verhindert, dass ein Fehler auftritt, wenn die Prozdur oder Funktion nicht existiert. Es wird eine Warnung ausgegeben, die mit SHOW WARNINGS angezeigt werden kann.

19.2.4. Syntax der CALL-Anweisung

CALL sp_name([parameter[,...]])

Die CALL-Anweisung ruft eine mit CREATE PROCEDURE erstellte Prozedur auf.

CALL kann Werte mithilfe von OUT- oder INOUT-Parametern an den Aufrufer zurückgeben. Außerdem kann CALL die Anzahl der betroffenen Zeilen „zurückgeben“, wenn auf der SQL-Ebene die Funktion ROW_COUNT() oder auf der C-Ebene die Funktion mysql_affected_rows() aus der C-API aufgerufen wird.

19.2.5. BEGIN ... END-Syntax für komplexe Anweisungen

[begin_label:] BEGIN
    [statement_list]
END [end_label]

Die BEGIN ... END-Syntax wird für zusammengesetzte Anweisungen verwendet, die in gespeicherten Routinen und Triggern auftreten können. Eine zusammengesetzte Anweisung enthält mehrere Anweisungen, die zwischen den Schlüsselwörtern BEGIN und END stehen. Die statement_list ist eine Liste mit einer oder mehreren Anweisungen. Jede Anweisung der statement_list muss durch ein Semikolon (;) abgegrenzt werden. Beachten Sie, dass eine statement_list optional ist, sodass auch eine leere zusammengesetzte Anweisung (BEGIN END) zulässig wäre.

Um überhaupt mehrere Anweisungen verbinden zu können, muss ein Client in der Lage sein, Strings von Anweisungen zu senden, die durch das Trennzeichen ; abgegrenzt sind. Dafür sorgt der Kommandozeilen-Client mysql mit dem Befehl delimiter. Wenn Sie das Begrenzungszeichen für Anweisungen von ; auf etwas anderes umstellen (beispielsweise ein //), dann kann ; auch im Rumpf der Routine verwendet werden. Ein Beispiel finden Sie in Abschnitt 19.2.1, „CREATE PROCEDURE und CREATE FUNCTION.

Eine zusammengesetzte Anweisung kann auch beschriftet sein. Ein end_label kann allerdings nur verwendet werden, wo auch ein begin_label vorhanden ist. Wo beide vorhanden sind, müssen sie identisch sein.

Die optionale Klausel [NOT] ATOMIC wird noch nicht unterstützt. Dies bedeutet, dass am Anfang eines Anweisungsblocks kein Transaktions-Savepoint gesetzt wird und dass eine BEGIN-Klausel in diesem Kontext keinen Einfluss auf die aktuelle Transaktion hat.

19.2.6. Syntax der DECLARE-Anweisung

Die DECLARE-Anweisung definiert Elemente als lokal in einer Routine:

Die Anweisungen SIGNAL und RESIGNAL werden zurzeit nicht unterstützt.

DECLARE ist nur in einer zusammengesetzten Anweisung mit BEGIN ... END zulässig und muss ganz am Anfang vor allen anderen Anweisungen stehen.

Deklarationen müssen in einer bestimmten Reihenfolge stehen. Cursors müssen vor Handlern und Variablen und Bedingungen vor Cursors und Handlern deklariert werden.

19.2.7. Variablen in gespeicherten Routinen

Auch Variablen können in einer Routine deklariert und benutzt werden.

19.2.7.1. Lokale DECLARE-Variablen

DECLARE var_name[,...] type [DEFAULT value]

Diese Anweisung deklariert lokale Variablen. Um der Variablen einen Standardwert beizugeben, schreiben Sie eine DEFAULT-Klausel in die Deklaration. Der Wert kann auch als Ausdruck angegeben werden, er muss nicht unbedingt eine Konstante sein. Ist keine DEFAULT-Klausel vorhanden, ist der Anfangswert der Variablen NULL.

Lokale Variablen werden im Hinblick auf Datentyp und Speicherüberlauf wie Routinenparameter behandelt. Siehe auch Abschnitt 19.2.1, „CREATE PROCEDURE und CREATE FUNCTION.

Eine lokale Variable gilt innerhalb des BEGIN ... END-Blocks, in dem sie deklariert ist. Die Variable kann auch in Blöcken verwendet werden, die in den deklarierenden Block eingeschachtelt sind, sofern dort nicht eine Variable mit demselben Namen deklariert ist.

19.2.7.2. Variable SET-Anweisung

SET var_name = expr [, var_name = expr] ...

Die SET-Anweisung in gespeicherten Routinen ist eine erweiterte Version der allgemeinen SET-Anweisung. Die Variablen können in einer Routine deklarierte Variablen oder globale Systemvariablen sein.

Die SET-Anweisung in gespeicherten Routinen ist als Teil der bereits existierenden SET-Syntax implementiert. Diese ermöglicht nämlich eine erweiterte Syntax mit SET a=x, b=y, ..., wobei verschiedene Variablentypen (lokal deklarierte Variablen sowie globale und Session-Servervariablen) vermischt werden können. Das ermöglicht auch Kombinationen von lokalen Variablen und einigen Optionen, die nur für Systemvariablen sinnvoll sind. In diesem Fall werden die Optionen zwar erkannt, aber ignoriert.

19.2.7.3. SELECT ... INTO-Anweisung

SELECT col_name[,...] INTO var_name[,...] table_expr

Diese SELECT-Syntax speichert die ausgewählten Spalten direkt in Variablen. Daher kann nur eine einzige Zeile abgerufen werden.

SELECT id,data INTO x,y FROM test.t1 LIMIT 1;

Bei den Namen von Benutzervariablen wird nicht zwischen Groß- und Kleinschreibung unterschieden. Siehe Abschnitt 9.3, „Benutzerdefinierte Variablen“.

Wichtig: In SQL dürfen Variablennamen und Spaltennamen nicht gleich sein. Wenn eine SQL-Anweisung wie etwa ein SELECT ... INTO eine Spalte und eine mit demselben Namen deklarierte lokale Variable benutzt, interpretiert MySQL in der gegenwärtigen Version diese Referenz als den Namen der Variablen. So wird beispielsweise xname in der folgenden Anweisung als die Variable und nicht als die Spalte mit dem Namen xname interpretiert:

CREATE PROCEDURE sp1 (x VARCHAR(5))
  BEGIN
    DECLARE xname VARCHAR(5) DEFAULT 'bob';
    DECLARE newname VARCHAR(5);
    DECLARE xid INT;
    
    SELECT xname,id INTO newname,xid 
      FROM table1 WHERE xname = xname;
    SELECT newname;
  END;

Wenn diese Prozedur aufgerufen wird, gibt die Variable newname immer den Wert 'bob' zurück, egal welchen Wert die Spalte table1.xname auch immer haben mag.

Siehe auch Abschnitt I.1, „Beschränkungen bei gespeicherten Routinen und Triggern“.

19.2.8. Bedingungen und Handler

Manche Bedingungen erfordern eine Sonderbehandlung, darunter Bedingungen, die Fehler oder den allgemeinen Kontrollfluss innerhalb einer Routine steuern.

19.2.8.1. DECLARE-Bedingungen

DECLARE condition_name CONDITION FOR condition_value

condition_value:
    SQLSTATE [VALUE] sqlstate_value
  | mysql_error_code

Diese Anweisung spezifiziert Bedingungen, die speziell behandelt werden müssen. Sie verbindet einen Namen mit einer Fehlerbedingung. Dieser Name kann danach in einer DECLARE HANDLER-Anweisung eingesetzt werden. Siehe Abschnitt 19.2.8.2, „DECLARE-Handler“.

Ein condition_value kann ein SQLSTATE-Wert oder ein MySQL-Fehlercode sein.

19.2.8.2. DECLARE-Handler

DECLARE handler_type HANDLER FOR condition_value[,...] statement

handler_type:
    CONTINUE
  | EXIT
  | UNDO

condition_value:
    SQLSTATE [VALUE] sqlstate_value
  | condition_name
  | SQLWARNING
  | NOT FOUND
  | SQLEXCEPTION
  | mysql_error_code

Die DECLARE ... HANDLER-Anweisung deklariert Handler, die jeweils eine oder mehrere Bedingungen behandeln können. Wenn eine dieser Bedingungen eintritt, wird das angegebene statement ausgeführt. statement kann eine einfache Anweisung sein (beispielsweise SET var_name = value) oder auch eine zusammengesetzte Anweisung in einem BEGIN ... END-Block (siehe Abschnitt 19.2.5, „BEGIN ... END-Syntax für komplexe Anweisungen“).

Ein CONTINUE-Handler lässt die Ausführung der aktuellen Routine nach der Ausführung der Handler-Anweisung weiterlaufen. Ein EXIT-Handler dagegen beendet die Ausführung für die zusammengesetzte BEGIN ... END-Anweisung, in der er deklariert ist. (Das gilt selbst dann, wenn die Bedingung in einem inneren Block eintritt.) Eine Anweisung des Typs UNDO-Handler wird zurzeit noch nicht unterstützt.

Wenn eine Bedingung eintritt, für die kein Handler deklariert wurde, ist die Standardaktion ein EXIT.

Ein condition_value kann einer der folgenden Werte sein:

  • Ein SQLSTATE-Wert oder MySQL-Fehlercode.

  • Ein zuvor mit DECLARE ... CONDITION deklarierter Bedingungsname. Siehe Abschnitt 19.2.8.1, „DECLARE-Bedingungen“.

  • SQLWARNING ist eine Abkürzung für alle SQLSTATE-Codes, die mit 01 beginnen.

  • NOT FOUND ist eine Abkürzung für alle SQLSTATE-Codes, die mit 02 beginnen.

  • SQLEXCEPTION ist eine Abkürzung für alle SQLSTATE-Codes, die nicht unter SQLWARNING oder NOT FOUND fallen.

Beispiel:

mysql> CREATE TABLE test.t (s1 int,primary key (s1));
Query OK, 0 rows affected (0.00 sec)

mysql> delimiter //

mysql> CREATE PROCEDURE handlerdemo ()
    -> BEGIN
    ->   DECLARE CONTINUE HANDLER FOR SQLSTATE '23000' SET @x2 = 1;
    ->   SET @x = 1;
    ->   INSERT INTO test.t VALUES (1);
    ->   SET @x = 2;
    ->   INSERT INTO test.t VALUES (1);
    ->   SET @x = 3;
    -> END;
    -> //
Query OK, 0 rows affected (0.00 sec)

mysql> CALL handlerdemo()//
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT @x//
    +------+
    | @x   |
    +------+
    | 3    |
    +------+
    1 row in set (0.00 sec)

Dieses Beispiel verbindet einen Handler mit der Bedingung SQLSTATE 23000, die bei doppelten Schlüsselwerten eintritt. Beachten Sie, dass @x die 3 ist: Dies zeigt, dass MySQL die Prozedur bis zum Ende ausgeführt hat. Wäre die Zeile DECLARE CONTINUE HANDLER FOR SQLSTATE '23000' SET @x2 = 1; nicht vorhanden, hätte MySQL den Standardweg (EXIT) eingeschlagen, nachdem das zweite INSERT an dem PRIMARY KEY-Constraint gescheitert ist, und SELECT @x hätte eine 2 zurückgegeben.

Wenn Sie eine Bedingung ignorieren möchten, können Sie einen CONTINUE-Handler für sie deklarieren und mit einem leeren Block verbinden. Zum Beispiel:

DECLARE CONTINUE HANDLER FOR SQLWARNING BEGIN END;

19.2.9. Cursor

Einfache Cursors werden in gespeicherten Prozeduren und Funktionen unterstützt. Die Syntax ist dieselbe wie in eingebettetem SQL. Gegenwärtig sind Cursors asensitiv, nur-lesend und nicht scrollbar. Asensitiv bedeutet, dass der Server eine Kopie der Ergebnistabelle anfertigen kann, aber nicht muss.

Cursors müssen vor Handlern und Variablen und Bedingungen vor Cursors und Handlern deklariert werden.

Beispiel:

CREATE PROCEDURE curdemo()
BEGIN
  DECLARE done INT DEFAULT 0;
  DECLARE a CHAR(16);
  DECLARE b,c INT;
  DECLARE cur1 CURSOR FOR SELECT id,data FROM test.t1;
  DECLARE cur2 CURSOR FOR SELECT i FROM test.t2;
  DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1;

  OPEN cur1;
  OPEN cur2;

  REPEAT
    FETCH cur1 INTO a, b;
    FETCH cur2 INTO c;
    IF NOT done THEN
       IF b < c THEN
          INSERT INTO test.t3 VALUES (a,b);
       ELSE
          INSERT INTO test.t3 VALUES (a,c);
       END IF;
    END IF;
  UNTIL done END REPEAT;

  CLOSE cur1;
  CLOSE cur2;
END

19.2.9.1. Deklaration von Cursorn

DECLARE cursor_name CURSOR FOR select_statement

Diese Anweisung deklariert einen Cursor. Es können zwar mehrere Cursors in einer Routine deklariert werden, aber in einem gegebenen Block muss jeder Cursor einen eindeutigen Namen haben.

Die SELECT-Anweisung darf keine INTO-Klausel haben.

19.2.9.2. Cursor-OPEN-Anweisung

OPEN cursor_name

Diese Anweisung öffnet einen zuvor deklarierten Cursor.

19.2.9.3. Das Cursor-Statement FETCH

FETCH cursor_name INTO var_name [, var_name] ...

Diese Anweisung holt die nächste Zeile ab (sofern eine existiert). Hierzu verwendet sie den angegebenen, geöffneten Cursor und schiebt den Zeiger im Cursor um eins weiter.

19.2.9.4. Cursor-Statement CLOSE

CLOSE cursor_name

Diese Anweisung schließt einen zuvor geöffneten Cursor.

Wenn ein Cursor nicht explizit geschlossen wurde, wird er am Ende der zusammengesetzten Anweisung geschlossen, in der er deklariert ist.

19.2.10. Konstrukte für die Ablaufsteuerung

Die Konstrukte IF, CASE, LOOP, WHILE, REPLACE ITERATE und LEAVE sind vollständig implementiert.

Viele dieser Konstrukte können andere Anweisungen enthalten, wie die Grammatikspezifikationen in den nachfolgenden Abschnitten zeigen. Solche Konstrukte können auch geschachtelt werden. So kann beispielsweise eine IF-Anweisung eine WHILE-Schleife enthalten, die ihrerseits eine CASE-Anweisung enthält.

FOR-Schleifen werden zurzeit nicht unterstützt.

19.2.10.1. IF-Anweisung

IF search_condition THEN statement_list
    [ELSEIF search_condition THEN statement_list] ...
    [ELSE statement_list]
END IF

IF implementiert ein einfaches Bedingungskonstrukt. Wenn die search_condition zutrifft, wird die zugehörige SQL-Anweisungsliste ausgeführt. Trifft keine search_condition zu, wird die Anweisungsliste aus der ELSE-Klausel ausgeführt. Jede statement_list besteht aus einer oder mehreren Anweisungen.

Hinweis: Es gibt auch eine IF()-Funktion, die sich von der hier beschriebenen IF-Anweisung unterscheidet. Siehe Abschnitt 12.2, „Ablaufsteuerungsfunktionen“.

19.2.10.2. CASE-Anweisung

CASE case_value
    WHEN when_value THEN statement_list
    [WHEN when_value THEN statement_list] ...
    [ELSE statement_list]
END CASE

Oder:

CASE
    WHEN search_condition THEN statement_list
    [WHEN search_condition THEN statement_list] ...
    [ELSE statement_list]
END CASE

Die CASE-Anweisung für gespeicherte Routinen implementiert ein komplexes Bedingungskonstrukt. Wenn die search_condition zutrifft, wird die zugehörige SQL-Anweisungsliste ausgeführt. Trifft keine search_condition zu, wird die Anweisungsliste aus der ELSE-Klausel ausgeführt. Jede statement_list besteht aus einer oder mehreren Anweisungen.

Hinweis: Die Syntax der hier gezeigten CASE-Anweisung, die in gespeicherten Routinen zum Einsatz kommt, unterscheidet sich von der Syntax des CASE-Ausdrucks von SQL, die in Abschnitt 12.2, „Ablaufsteuerungsfunktionen“, beschrieben wird. Die CASE-Anweisung darf keine ELSE NULL-Klausel haben und wird mit END CASE anstelle von END abgeschlossen.

19.2.10.3. LOOP-Anweisung

[begin_label:] LOOP
    statement_list
END LOOP [end_label]

LOOP implementiert ein einfaches Schleifenkonstrukt, das eine wiederholte Ausführung der aus einer oder mehreren Anweisungen bestehenden Anweisungsliste ermöglicht. Die Anweisungen werden in der Schleife so lange wiederholt, bis die Schleife abgebrochen wird. Dies geschieht normalerweise mit der LEAVE-Anweisung.

Eine LOOP-Anweisung kann auch beschriftet sein. Ein end_label kann allerdings nur verwendet werden, wo auch ein begin_label vorhanden ist. Wo beide vorhanden sind, müssen sie identisch sein.

19.2.10.4. LEAVE-Anweisung

LEAVE label

Diese Anweisung beendet ein beschriftetes Konstrukt zur Flusskontrolle. Sie kann in einem BEGIN ... END-Block oder in Schleifenkonstrukten (LOOP, REPEAT, WHILE) verwendet werden.

19.2.10.5. ITERATE-Anweisung

ITERATE label

ITERATE kann nur in LOOP-, REPEAT- und WHILE-Anweisungen auftreten. ITERATE bedeutet „Durchlaufe die Schleife noch einmal“.

Beispiel:

CREATE PROCEDURE doiterate(p1 INT)
BEGIN
  label1: LOOP
    SET p1 = p1 + 1;
    IF p1 < 10 THEN ITERATE label1; END IF;
    LEAVE label1;
  END LOOP label1;
  SET @x = p1;
END

19.2.10.6. REPEAT-Anweisung

[begin_label:] REPEAT
    statement_list
UNTIL search_condition
END REPEAT [end_label]

Die Anweisungsliste in einer REPEAT-Anweisung wird wiederholt, bis die Suchbedingung search_condition zutrifft. Somit geht ein REPEAT immer mindestens einmal in die Schleife. Die statement_list besteht aus einer oder mehreren Anweisungen.

Eine REPEAT-Anweisung kann auch beschriftet sein. Ein end_label kann allerdings nur verwendet werden, wo auch ein begin_label vorhanden ist. Wo beide vorhanden sind, müssen sie identisch sein.

Beispiel:

mysql> delimiter //

mysql> CREATE PROCEDURE dorepeat(p1 INT)
    -> BEGIN
    ->   SET @x = 0;
    ->   REPEAT SET @x = @x + 1; UNTIL @x > p1 END REPEAT;
    -> END
    -> //
Query OK, 0 rows affected (0.00 sec)

mysql> CALL dorepeat(1000)//
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT @x//
+------+
| @x   |
+------+
| 1001 |
+------+
1 row in set (0.00 sec)

19.2.10.7. WHILE-Anweisung

[begin_label:] WHILE search_condition DO
    statement_list
END WHILE [end_label]

Die Anweisungsliste in einer WHILE-Anweisung wird wiederholt, solange die search_condition zutrifft. Die statement_list besteht aus einer oder mehreren Bedingungen.

Eine WHILE-Anweisung kann auch beschriftet sein. Ein end_label kann allerdings nur verwendet werden, wo auch ein begin_label vorhanden ist. Wo beide vorhanden sind, müssen sie identisch sein.

Beispiel:

CREATE PROCEDURE dowhile()
BEGIN
  DECLARE v1 INT DEFAULT 5;

  WHILE v1 > 0 DO
    ...
    SET v1 = v1 - 1;
  END WHILE;
END

19.3. Gespeicherte Prozeduren, Funktionen, Trigger und Replikation: häufig gestellte Fragen

  • Funktionieren die gespeicherten Prozeduren von MySQL 5.1 mit Replikation?

    Ja. Standardaktionen, die in gespeicherten Prozeduren und Funktionen ausgeführt werden, werden von einem MySQL-Master-Server auf einen Slave-Server repliziert. Es gibt einige wenige Beschränkungen, die in Abschnitt 19.4, „Binärloggen gespeicherter Routinen und Trigger“ genauer ausgeführt werden.

  • Werden gespeicherte Prozeduren und Funktionen, die auf einem Master-Server angelegt wurden, auf einen Slave repliziert?

    Ja, gespeicherte Prozeduren und Funktionen, die mit normalen DDL-Anweisungen auf einem Master-Server angelegt wurden, werden auf einen Slave repliziert, sodass die Objekte auf beiden Servern vorhanden sind. ALTER- und DROP-Anweisungen für gespeicherte Prozeduren und Funktionen werden ebenfalls repliziert.

  • Wie werden Aktionen repliziert, die innerhalb von gespeicherten Prozeduren und Funktionen stattfinden?

    MySQL zeichnet jedes DML-Ereignis auf, das in einer gespeicherten Prozedur auftritt, und repliziert diese Einzelaktionen auf einen Slave-Server. Die eigentlichen Aufrufe der gespeicherten Prozeduren werden nicht repliziert.

    Gespeicherte Funktionen, die Daten ändern, werden als Funktionsaufrufe ins Log geschrieben, und nicht als die DML-Ereignisse, die innerhalb der Funktionen stattfinden.

  • Gibt es spezielle Sicherheitsanforderungen, um gespeicherte Prozeduren und Funktionen mit Replikation zu benutzen?

    Ja. Da ein Slave-Server das Recht hat, jede Anweisung auszuführen, die er aus dem Binärlog seines Masters liest, gibt es besondere Sicherheitsvorkehrungen für die Verwendung von gespeicherten Prozeduren mit Replikation. Wenn Replikation oder Binärlogging im Allgemeinen (für die Point-of-Time-Recovery) aktiv ist, haben MySQL-DBAs zwei Sicherheitsoptionen:

    • Option 1: Ein Benutzer, der eine gespeicherte Funktion anlegen möchte, muss das SUPER-Recht besitzen.

    • Option 2: Alternativ kann ein DBA die Systemvariable log_bin_trust_function_creators auf 1 setzen. Dann kann jeder, der über die Standardberechtigung CREATE ROUTINE verfügt, gespeicherte Funktionen erstellen.

  • Welche Beschränkungen gelten für die Replikation von Aktionen gespeicherter Prozeduren und Funktionen?

    Nichtdeterministische (zufällige) oder zeitabhängige Aktionen, die in gespeicherten Prozeduren eingebettet sind, werden eventuell nicht richtig repliziert. Nach dem Zufallsprinzip entstandene Ergebnisse sind von Natur aus unvorhersehbar und lassen sich nicht genau reproduzieren. Somit spiegeln Zufallsaktionen, die auf einen Slave repliziert werden, nicht die Aktionen des Masters wider. Wenn Sie gespeicherte Funktionen als DETERMINISTIC deklarieren oder die Systemvariable log_bin_trust_function_creators auf 0 setzen, können keine Operationen mit Zufallswerten aufgerufen werden.

    Auch zeitabhängige Aktionen lassen sich nicht auf einen Slave replizieren, da das Timing solcher Aktionen in einer gespeicherten Prozedur nicht durch das für die Replikation eingesetzte Binärlog reproduzierbar ist. Dieses zeichnet nur DML-Ereignisse auf und berücksichtigt keine Zeiteinschränkungen.

    Auch in nichttransaktionssicheren Tabellen, mit denen bei größeren DML-Aktionen (wie beispielsweise Massen-Einfügeoperationen) Fehler auftreten, können Replikationsprobleme entstehen, wenn ein Master aufgrund der DML-Aktivität teilweise aktualisiert wurde, während der Slave wegen eines Fehlers diese Änderung nicht mitgemacht hat. Dies kann man umgehen, indem man die DML-Aktionen einer Funktion mit dem Schlüsselwort IGNORE ausführt, damit Änderungen auf dem Master, die Fehler auslösen, ignoriert und Änderungen, die keine Fehler auslösen, auf den Slave repliziert werden.

  • Beeinträchtigen die vorgenannten Beschränkungen die Fähigkeit von MySQL, eine Point-in-Time-Recovery durchzuführen?

    Dieselben Beschränkungen, die sich auf die Replikation auswirken, wirken sich auch auf die Point-in-Time-Recovery aus.

  • Was unternimmt MySQL, um diese Beschränkungen auszuräumen?

    Ab MySQL 5.1.5 können Sie zwischen anweisungs- und zeilenbasierter Replikation wählen. Die ursprüngliche Implementierung der Replikation beruht auf dem anweisungsbasierten Binärlogging. Zeilenbasiertes Binärlogging löst die oben beschriebenen Probleme. Mehr zum Thema finden Sie unter Abschnitt 6.3, „Zeilenbasierte Replikation“.

  • Funktionieren Trigger mit Replikation?

    Trigger und Replikation funktionieren in MySQL 5.1 genau wie in den meisten anderen Datenbank-Engines: Aktionen, die auf einem Master mithilfe von Triggern ausgeführt werden, werden nicht auf einen Slave-Server repliziert. Dagegen müssen Trigger auf Tabellen, die auf einem MySQL-Master-Server liegen, auch auf den entsprechenden Tabellen des MySQL-Slave-Servers angelegt werden, um auf den Slaves ebenso wie auf dem Master aktiviert werden zu können.

  • Wie werden Trigger-Aktionen auf einem Master ausgeführt, der auf einen Slave repliziert wurde?

    Erstens müssen die Trigger, die auf dem Master vorhanden sind, auf dem Slave-Server rekonstruiert werden. Wenn dies erledigt ist, läuft die Replikation so ab wie bei jeder anderen DML-Standardanweisung, die an einer Replikation beteiligt ist. Betrachten Sie als Beispiel die Tabelle EMP mit dem Insert-Trigger AFTER, die auf einem MySQL-Master-Server liegt. Dieselbe EMP-Tabelle mit demselben AFTER-Insert-Trigger liegt auch auf dem Slave-Server. Der Replikationsfluss verliefe folgendermaßen:

  1. Eine INSERT-Anweisung für EMP wird abgesetzt.

  2. Der AFTER-Trigger auf EMP wird aktiviert.

  3. Die INSERT-Anweisung wird in das Binärlog geschrieben.

  4. Der Replikations-Slave nimmt die INSERT-Anweisung für EMP auf und führt sie aus.

  5. Der AFTER-Trigger auf EMP, der auf dem Slave existiert, wird aktiviert.

19.4. Binärloggen gespeicherter Routinen und Trigger

Das Binärlog enthält Informationen über SQL-Anweisungen, die Datenbankinhalte ändern. Diese Informationen werden in Form von „Ereignissen“ gespeichert, welche die Modifikationen beschreiben. Das Binärlog dient zwei wichtigen Zwecken:

  • Für die Replikation sendet der Master-Server die Ereignisse, die in seinem Binärlog stehen, an seine Slaves. Diese führen die Ereignisse dann aus, um die Datenänderungen nachzuvollziehen, die auf dem Master stattgefunden haben. Siehe Abschnitt 6.2, „Replikation: Implementation“.

  • Für bestimmte Datenwiederherstellungsoperationen muss das Binärlog genutzt werden. Nach der Wiederherstellung einer Sicherungsdatei werden aus dem Binärlog die Ereignisse, die nach Erstellung der Sicherungsdatei eintraten, erneut ausgeführt. Diese Ereignisse bringen die Datenbank von dem Zeitpunkt der Sicherung auf den neuesten Stand. Siehe auch Abschnitt 5.10.2.2, „Verwenden von Datensicherungen zur Wiederherstellung“.

Dieser Abschnitt beschreibt, wie MySQL 5.1 das Binärlogging für gespeicherte Routinen (Prozeduren und Funktionen) und Trigger behandelt. Es wird beschrieben, welche Bedingungen die Implementierung zurzeit an die Verwendung gespeicherter Routinen knüpft, und diese Bedingungen werden auch begründet.

Die hier beschriebenen Probleme gründen im Wesentlichen auf die Tatsache, dass Binärlogging auf SQL-Anweisungsebene stattfindet. In einem künftigen Release von MySQL soll auch Binärlogging auf Zeilenebene eingeführt werden, wobei die Änderungen protokolliert werden, die bei der Ausführung von SQL-Anweisungen in den einzelnen Zeilen stattfinden.

Soweit nichts anderes gesagt wird, gehen wir in den nachfolgenden Bemerkungen davon aus, dass Sie Binärlogging durch Hochfahren des Servers mit der Option --log-bin eingeschaltet haben. (Siehe auch Abschnitt 5.12.3, „Die binäre Update-Logdatei“.) Wenn das Binärlog nicht eingeschaltet ist, ist weder eine Replikation möglich noch steht das Binärlog für die Wiederherstellung von Daten zur Verfügung.

Die derzeit gültigen Bedingungen für die Nutzung gespeicherter Funktionen in MySQL 5.1 werden im Folgenden zusammengefasst. Diese Bedingungen gelten nicht für gespeicherte Prozeduren, und sie gelten auch ansonsten nur dann, wenn das Binärlogging auch eingeschaltet ist.

  • Um eine gespeicherte Funktion anzulegen oder zu ändern, benötigen Sie das SUPER-Recht zusätzlich zu dem normalerweise erforderlichen CREATE ROUTINE- oder ALTER ROUTINE-Recht.

  • Wenn Sie eine gespeicherte Funktion erstellen, müssen Sie sie entweder als deterministisch deklarieren oder festlegen, dass sie keine Daten modifiziert. Andernfalls kann sie für die Datenwiederherstellung oder Replikation Unsicherheiten bergen.

  • Zur Lockerung der obigen Bedingungen für die Erstellung einer Funktion (obligatorisches SUPER-Recht und das Erfordernis, entweder eine deterministische Funktion oder eine Funktion, die keine Daten ändert, zu deklarieren) können Sie die globale Systemvariable log_bin_trust_function_creators auf 1 setzen. Diese Variable hat den Standardwert 0, lässt sich aber folgendermaßen umstellen:

    mysql> SET GLOBAL log_bin_trust_function_creators = 1;
    

    Sie können diese Variable auch einstellen, indem Sie beim Starten des Servers die Option --log-bin-trust-function-creators einstellen.

    Wenn Binärlogging nicht aktiviert ist, ist log_bin_trust_function_creators unwirksam und das SUPER-Recht ist nicht erforderlich, um eine Funktion anzulegen.

Da Trigger gespeicherten Funktionen ähneln, gelten diese Ausführungen zu Funktionen auch für Trigger, allerdings mit der folgenden Einschränkung: Da CREATE TRIGGER kein optionales DETERMINISTIC-Merkmal hat, geht man davon aus, dass Trigger immer deterministisch sind. Doch diese Grundannahme muss nicht immer gelten. So ist beispielsweise die Funktion UUID() nichtdeterministisch (und wird auch nicht repliziert). Solche Funktionen sollten Sie in Triggern nur mit Vorsicht benutzen.

Da Trigger Tabellen ändern können, kommen im Zusammenhang mit CREATE TRIGGER ähnliche Fehlermeldungen wie bei gespeicherten Funktionen vor, wenn Sie nicht das SUPER-Recht haben und log_bin_trust_function_creators den Wert 0 hat.

Im Folgenden erfahren Sie mehr über die Logging-Implementierung und ihre Implikationen.

  • Der Server schreibt CREATE PROCEDURE-, CREATE FUNCTION-, ALTER PROCEDURE-, ALTER FUNCTION-, DROP PROCEDURE- und DROP FUNCTION-Anweisungen in das Binärlog.

  • Der Aufruf einer gespeicherten Funktion wird als DO-Anweisung protokolliert, wenn die Funktion Daten ändert und in einer Anweisung auftritt, die ansonsten nicht protokolliert würde. Dadurch wird verhindert, dass Datenänderungen, die durch die Verwendung von gespeicherten Funktionen in nichtprotokollierten Anwendungen auftreten, nicht repliziert werden. So werden beispielsweise SELECT-Anweisungen nicht in das Binärlog geschrieben, aber ein SELECT kann eine gespeicherte Funktion aufrufen, die Datenänderungen hervorruft. Um damit umzugehen, wird eine DO func_name()-Anweisung in das Binärlog geschrieben, wenn die Funktion eine Änderung vornimmt. Angenommen, folgende Funktionen werden auf dem Master ausgeführt:

    CREATE FUNCTION f1(a INT) RETURNS INT
    BEGIN
      IF (a < 3) THEN 
        INSERT INTO t2 VALUES (a);
      END IF;
    END;
    
    CREATE TABLE t1 (a INT);
    INSERT INTO t1 VALUES (1),(2),(3);
    
    SELECT f1(a) FROM t1;
    

    Wenn die SELECT-Anweisung ausgeführt wird, wird die Funktion f1() dreimal aufgerufen. Zwei der Aufrufe fügen eine Zeile ein und MySQL schreibt für jede von ihnen eine DO-Anweisung in das Log. Somit hält MySQL folgende Anweisungen im Binärlog fest:

    DO f1(1);
    DO f1(2);
    

    Der Server protokolliert auch eine DO-Anweisung für einen Aufruf einer gespeicherten Funktion, wenn die Funktion eine gespeicherte Prozedur aufruft, die einen Fehler verursacht. In diesem Fall schreibt der Server die DO-Anweisung zusammen mit dem erwarteten Fehlercode in das Log. Wenn auf dem Slave derselbe Fehler auftritt, ist dies das erwartete Resultat und die Replikation läuft weiter. Andernfalls bricht die Replikation ab.

  • Wenn anstelle von Anweisungen, die eine Funktion ausführt, Aufrufe gespeicherter Funktionen protokolliert werden, hat dies Folgen für die Sicherheit der Replikation. Dafür sind zwei Faktoren verantwortlich:

    • Es ist möglich, dass eine Funktion auf dem Master und den Slave-Servern unterschiedliche Ausführungspfade einschlägt.

    • Anweisungen, die auf einem Slave ausgeführt werden, werden von dem SQL-Thread des Slaves verarbeitet, der volle Berechtigungen hat.

    Die Folge davon ist, dass zwar jeder Benutzer für die Erstellung einer Funktion das CREATE ROUTINE-Recht benötigt. Doch damit könnte er eine Funktion schreiben, die eine gefährliche Anweisung enthält, die nur auf dem Slave ausgeführt wird, da sie dort von dem SQL-Thread ausgeführt wird, der über volle Berechtigungen verfügt. Wenn beispielsweise der Master-Server die Server-ID 1 und der Slave-Server die Server-ID 2 hätte, könnte ein Benutzer auf dem Master-Server eine unsichere Funktion namens unsafe_func() folgendermaßen erstellen und aufrufen:

    mysql> delimiter //
    mysql> CREATE FUNCTION unsafe_func () RETURNS INT
        -> BEGIN
        ->   IF @@server_id=2 THEN dangerous_statement; END IF;
        ->   RETURN 1;
        -> END;
        -> //
    mysql> delimiter ;
    mysql> INSERT INTO t VALUES(unsafe_func());
    

    Da die CREATE FUNCTION- und die INSERT-Anweisung in das Binärlog geschrieben werden, führt der Slave sie aus. Und da der Slave-SQL-Thread wiederum über so umfassende Berechtigungen verfügt, wird er die gefährliche Anweisung auch befolgen. Somit hat der Funktionsaufruf auf dem Master andere Folgen als auf dem Slave und ist nicht replikationssicher.

    Um Server, auf denen das Binärlogging eingeschaltet ist, vor dieser Gefahr zu schützen, benötigen die Erzeuger gespeicherter Funktionen zusätzlich zu dem üblichen CREATE ROUTINE-Recht, das ohnehin notwendig ist, auch das SUPER-Recht. Ebenso darf ALTER FUNCTION nur benutzen, wer zusätzlich zu dem ALTER ROUTINE-Recht auch das SUPER-Recht besitzt. Fehlt die SUPER-Berechtigung, wird ein Fehler ausgelöst:

    ERROR 1419 (HY000): You do not have the SUPER privilege and
    binary logging is enabled (you *might* want to use the less safe
    log_bin_trust_function_creators variable)
    

    Wenn Sie von den Erzeugern von Funktionen nicht verlangen möchten, dass sie das SUPER-Recht besitzen (zum Beispiel, wenn in Ihrem System alle Benutzer, die das CREATE ROUTINE-Recht haben, erfahrene Anwendungsentwickler sind), dann können Sie die globale Systemvariable log_bin_trust_function_creators auf 1 setzen. Das können Sie auch tun, indem Sie beim Serverstart die Option --log-bin-trust-function-creators einstellen. Wenn kein Binärlogging aktiviert ist, ist log_bin_trust_function_creators wirkungslos und zum Anlegen von Funktionen ist kein SUPER-Recht erforderlich.

  • Wenn eine Funktion, die Änderungen vornimmt, nichtdeterministisch ist, so ist sie auch nicht wiederholbar. Dies kann zwei unangenehme Folgen haben:

    • Ein Slave entspricht nicht mehr dem Master.

    • Wiederhergestellte Daten entsprechen nicht mehr den Originaldaten.

    Um diese Probleme in den Griff zu bekommen, stellt MySQL folgende Anforderung: Auf einem Master-Server ist die Erzeugung und Änderung einer Funktion nur möglich, wenn diese als deterministisch deklariert ist oder keine Daten ändert. Hierbei kommen zwei Arten von Funktionsmerkmalen ins Spiel:

    • Die Merkmale DETERMINISTIC und NOT DETERMINISTIC zeigen an, ob eine Funktion für dieselben Eingabewerte auch immer dieselben Ergebnisse produziert. Da die Standardeinstellung, wenn nichts anderes angegeben wird, NOT DETERMINISTIC lautet, müssen Sie explizit das Merkmal DETERMINISTIC angeben, um klarzustellen, dass eine Funktion deterministisch ist.

      Durch Verwendung der Funktion NOW() (oder ihrer Synonyme) oder RAND() wird eine Funktion nicht unbedingt deterministisch. Für NOW() zeichnet das Binärlog den Zeitstempel auf und repliziert richtig. Die Funktion RAND() repliziert ebenfalls korrekt, sofern sie in einer Funktion nicht mehrmals aufgerufen wird. (Den Ausführungs-Zeitstempel der Funktion und den Zufallszahlen-Seedwert können Sie als implizite Eingaben betrachten, die bei Master und Slave identisch sind.)

    • Die Merkmale CONTAINS SQL, NO SQL, READS SQL DATA und MODIFIES SQL DATA geben Informationen darüber, ob die Funktion Daten liest oder schreibt. NO SQL oder READS SQL DATA zeigt an, dass eine Funktion keine Daten ändert, aber Sie müssen eines dieser Merkmale explizit angeben, da ansonsten der Standardwert CONTAINS SQL ist.

    Damit eine CREATE FUNCTION-Anweisung akzeptiert wird, muss nach Voreinstellung DETERMINISTIC bzw. entweder NO SQL oder READS SQL DATA explizit angegeben werden. Andernfalls tritt ein Fehler auf:

    ERROR 1418 (HY000): This function has none of DETERMINISTIC, NO SQL,
    or READS SQL DATA in its declaration and binary logging is enabled
    (you *might* want to use the less safe log_bin_trust_function_creators
    variable)
    

    Wenn Sie log_bin_trust_function_creators auf 1 setzen, wird nicht mehr gefordert, dass Funktionen entweder deterministisch sind oder keine Daten ändern.

    Wie die Natur einer Funktion eingeschätzt wird, hängt von der „Ehrlichkeit“ ihres Erzeugers ab: MySQL prüft nicht, ob eine als DETERMINISTIC deklarierte Funktion auch tatsächlich keine Anweisungen enthält, die nichtdeterministische Ergebnisse produzieren.

  • Aufrufe an gespeicherte Prozeduren werden auf Anweisungs- statt auf CALL-Ebene protokolliert. Das bedeutet, dass der Server nicht die CALL-Anweisung protokolliert, sondern diejenigen Anweisungen der Prozedur, die tatsächlich ausgeführt werden. Dadurch treten auf den Slave-Servern dieselben Änderungen wie auf dem Master ein. So werden Probleme vermieden, die entstehen könnten, wenn eine Prozedur auf verschiedenen Rechnern verschiedene Ausführungspfade hat.

    Im Allgemeinen werden Anweisungen, die in einer gespeicherten Prozedur ausgeführt werden, nach denselben Regeln in das Binärlog geschrieben, die auch für eigenständig ausgeführte Anweisungen gelten. Die Protokollierung von Prozeduranweisungen wird besonders vorsichtig gehandhabt, da die Ausführung von Anweisungen innerhalb einer Prozedur nicht dasselbe ist wie außerhalb einer Prozedur:

    • Eine zu protokollierende Anweisung könnte Verweise auf lokale Prozedurvariablen enthalten. Da diese Variablen außerhalb des Prozedurkontextes gar nicht vorhanden sind, kann eine Anweisung, die eine solche Variable benutzt, nicht wörtlich protokolliert werden. Stattdessen wird für das Log jede Referenz auf eine lokale Variable durch das folgende Konstrukt ersetzt:

      NAME_CONST(var_name, var_value)
      

      var_name ist der Name der lokalen Variablen und var_value eine Konstante, die den Wert der Variablen zum Zeitpunkt der Protokollierung der Anweisung anzeigt. NAME_CONST() hat den Wert var_value und den „Namenvar_name. Somit erhalten Sie folgendes Ergebnis, wenn Sie diese Funktion direkt aufrufen:

      mysql> SELECT NAME_CONST('myname', 14);
      +--------+
      | myname |
      +--------+
      |     14 |
      +--------+
      

      NAME_CONST() ermöglicht es, eine selbstständige Anweisung auf einem Slave so auszuführen, dass sie denselben Effekt hat wie die Originalanweisung, die innerhalb einer gespeicherten Prozedur auf dem Master-Server ausgeführt wurde.

    • Eine zu protokollierende Anweisung könnte Referenzen auf benutzerdefinierte Variablen enthalten. Also schreibt MySQL eine SET-Anweisung in das Binärlog, um zu gewährleisten, dass die Variable auf dem Slave mit demselben Wert wie auf dem Master existiert. Wenn beispielsweise eine Anweisung die Variable @my_var benutzt, steht vor dieser Anweisung im Binärlog folgende Anweisung, wobei value der Wert ist, den @my_var auf dem Master hat:

      SET @my_var = value;
      
    • Prozeduraufrufe können in einer committeten oder zurückgerollten Transaktion auftreten. Früher wurden CALL-Anweisungen auch dann protokolliert, wenn sie in einer zurückgerollten Transaktion vorkamen. Seit MySQL 5.0.12 wird der Transaktionskontext berücksichtigt, sodass die transaktionsbedingten Aspekte der Prozedurausführung korrekt repliziert werden. Das bedeutet, dass der Server in der Prozedur nur diejenigen Anweisungen protokolliert, die auch tatsächlich ausgeführt werden und Daten ändern. Nach Bedarf protokolliert er darüber hinaus auch BEGIN-, COMMIT- und ROLLBACK-Anweisungen. Wenn beispielsweise eine Prozedur nur Transaktionstabellen ändert und innerhalb einer Transaktion ausgeführt wird, die zurückgerollt wird, werden solche Änderungen nicht ins Log geschrieben. Tritt die Prozedur hingegen in einer committeten Transaktion auf, werden BEGIN- und COMMIT-Anweisungen mit den Updates protokolliert. Die Anweisungen einer in einer zurückgerollten Transaktion ausgeführten Prozedur werden nach denselben Regeln protokolliert, die auch gelten würden, wenn die Anweisungen selbstständig ausgeführt worden wären:

      • Änderungen an Transaktionstabellen werden nicht protokolliert.

      • Änderungen an anderen Tabellen werden protokolliert, weil sie durch ein Rollback nicht rückgängig gemacht werden.

      • Änderungen an einem Mix von Transaktionstabellen und anderen Tabellen werden innerhalb eines BEGIN-ROLLBACK Blocks protokolliert, damit die Slaves dieselben Änderungen und Rollbacks wie der Master vornehmen.

  • Ein Aufruf einer gespeicherten Prozedur wird nicht auf Anweisungsebene in das Binärlog geschrieben, wenn die Prozedur innerhalb einer gespeicherten Funktion aufgerufen wird. In einem solchen Fall wird lediglich die Anweisung protokolliert, welche die Funktion aufruft, (wenn diese innerhalb einer protokollierten Anweisung benutzt wird), oder eine DO-Anweisung (wenn sie in einer Anweisung benutzt wird, die nicht protokolliert wird). Daher sollten Sie vorsichtig mit gespeicherten Funktionen sein, die eine Prozedur aufrufen, selbst wenn die Prozedur ansonsten sicher ist.


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.