MariaDB 10.1.1:复合语句

有时需要有条件地执行某些 SQL 语句。如果您从 PHP(或 Java 或其他任何语言)应用程序中执行此操作,这很容易。但如果只有纯 SQL 呢?MariaDB 和 MySQL 在 mysql_fix_privilege_tables.sql 脚本中(由 mysql_upgrade 工具应用)使用了两种技术。

  1. 创建一个包含 IF 语句的存储过程,调用一次然后将其丢弃。这要求用户拥有 CREATE ROUTINE 权限,并且 mysql.proc 表必须存在且可用(这不一定为真——我们正在从 mysql_upgrade 中执行,对吧?)。
  2. 使用动态 SQL,例如
    SET @str = IF (@have_csv = 'YES',
                   'CREATE TABLE IF NOT EXISTS general_log (
                      event_time TIMESTAMP(6) NOT NULL,
                      user_host MEDIUMTEXT NOT NULL,
                      thread_id BIGINT(21) UNSIGNED NOT NULL,
                      server_id INTEGER UNSIGNED NOT NULL,
                      command_type VARCHAR(64) NOT NULL,
                      argument MEDIUMTEXT NOT NULL
                    ) engine=CSV CHARACTER SET utf8 comment="General log"',
                   'SET @dummy = 0');
    
    PREPARE stmt FROM @str;
    EXECUTE stmt;
    DROP PREPARE stmt;
    

    这种方式可读性不高,如果您需要有条件地执行许多语句,则效果不佳。

这可能是标准做法,但我从未理解为什么不能直接在 mysql 命令行工具提示符下简单地使用 SQL 控制语句(除了 CALL)。想象一个 bash 变体,它只在脚本中支持 ifwhile,但在命令行交互模式下不支持——您会喜欢它吗?

也许 Antony Curtis 在为 MDEV-5317 中的复合语句贡献补丁时,也问过自己类似的问题。无论如何,我们认为这是一个好主意,并基于 Antony 的贡献实现了此功能。

现在可以直接在 SQL 脚本和 mysql 命令行提示符下使用 BEGIN, IF, CASE, WHILE, LOOP, REPEAT 语句——无需在存储程序中。例如,可以将上面的内容重写为

IF @have_csv = 'YES' THEN
  CREATE TABLE IF NOT EXISTS general_log (
    event_time TIMESTAMP(6) NOT NULL,
    user_host MEDIUMTEXT NOT NULL,
    thread_id BIGINT(21) UNSIGNED NOT NULL,
    server_id INTEGER UNSIGNED NOT NULL,
    command_type VARCHAR(64) NOT NULL,
    argument MEDIUMTEXT NOT NULL
  ) engine=CSV CHARACTER SET utf8 comment="General log";
END IF

可以使用 BEGIN ... END 块和循环,而无需 CREATE ROUTINE 权限,即使 mysql.proc 表损坏(或丢失)也可以。这一切都如您所期望地那样工作,没有“标准规定如此”的人为限制。

不过,仍有一些需要注意的限制

  • 您不能使用简单的 BEGIN 来启动块,这在历史上用于启动事务。请使用标准语法 BEGIN NOT ATOMIC
  • 来自 mysql 命令行提示符的复合语句不能以标签开头。
  • 并非所有可以在存储程序中使用的语句都支持在 mysql 命令行提示符下使用,只有上面列出的语句支持。

不过,这些限制仅适用于顶级语句。例如,如果您需要带标签的循环或 SIGNAL,则可以启动一个块并将语句放在其中

BEGIN NOT ATOMIC
  DECLARE v1 INT DEFAULT 500;
  label1:
  WHILE v1 > 0 DO
    INSERT t1 (v) VALUES (v1);
    SET v1 = v1 - 1;
    IF (SELECT COUNT(*) FROM t1) > 500 THEN
      LEAVE label1;
    END IF;
  END WHILE label1;
END

享受吧!