【问题标题】:Abort MySQL script conditionally有条件地中止 MySQL 脚本
【发布时间】:2011-01-01 23:50:07
【问题描述】:

给定:MySQL 数据库。有时会推出 db 架构更改和更新(以 sql 脚本的形式)。为了保证应用更新的正确顺序(没有重复更新、没有更新丢失等),我计划部署以下解决方案:

  • CREATE TABLE 元(名称 TEXT PRIMARY KEY,val TEXT);
  • INSERT INTO 元值('version','0');

每个更新脚本都带有一个按顺序分配的版本 N。在执行更新之前,脚本会检查 meta.version 是否与之前的脚本版本 N-1 匹配。执行更新后,meta.version 更新为 N。我不需要防止多个脚本并行运行。

问题:如果不匹配,如何检查版本并中止脚本?我发现执行

CALL `raise error`

会破坏脚本,但是如何根据 meta.version 有条件地执行它?不允许存储过程。有意义的错误信息是一个加号。 This 没有提供合适的解决方案。

【问题讨论】:

    标签: mysql error-handling sql-scripts


    【解决方案1】:

    我认为这最好在 shell 脚本或安装程序中解决。您不能在 SQL 脚本中真正拥有任何控制结构。

    一个非常简单的解决方案是创建一个脚本,该脚本根据元表上的选择生成命令行以供实际脚本执行。

    假设你有这个脚本,gen_upgrade_command.sql:

    select concat(
           'mysql -u<user> -p<password> -h<host> -e"SOURCE upgrade'
       ,    val + 1
       ,   '.sql"'
       )
    from meta;
    

    你是这样跑的

    mysql -u<user> -p<password> -h<host> -Nrs < gen_upgrade_command.sql > do_upgrade.bat
    

    现在,do_upgrade.bat 包含这个生成的命令行:

    mysql -u<usere> -p<password> -h<host> -e"SOURCE upgrade1.sql"
    

    运行do_upgrade.bat 将运行upgrade1.sql

    当然,您也可以修改原始脚本以完全不选择任何行,这取决于您。

    【讨论】:

    • 是的,这完全可以通过包装脚本实现,但是我需要确保每个人都始终使用这个包装脚本进行更新。这肯定行不通。我需要“自动”解决方案 - sql 更新脚本是否有效。
    【解决方案2】:

    好的,新答案。与其说是赏金让我更有创造力,倒不如说我对提出解决方案没有那么拘谨我不认为是直截了当的:)

    (我仍然假设您想不惜一切代价避免存储例程)

    解决方案 #1:使用准备好的语句

    假设你的升级脚本很简单:

    ALTER TABLE t ADD COLUMN c INT DEFAULT 1
    

    那么这个怎么样:

    SET @version := '1';
    
    SELECT CASE val
               WHEN @version THEN 
                   'ALTER TABLE t ADD COLUMN c INT DEFAULT 1'
               ELSE 'SELECT ''Wrong version. Nothing to upgrade.'''
           END
    INTO   @stmt
    FROM   meta 
    WHERE  name = 'Version';
    
    PREPARE stmt FROM @stmt;
    EXECUTE stmt;
    

    明显的缺点:

    • 避免混乱和开销,因为您必须为每个单独的语句编写此内容(PREPARE 不能批处理多个语句)
    • 额外的烦恼,因为您必须将升级脚本中的每个语句编写为字符串...哎呀
    • 并非所有语句都可以用PREPARE 编写和执行

    解决方案 #2:使用一个准备好的语句来终止连接

    您已经指出您也不喜欢您链接的解决方案...是因为KILL 语句,还是因为存储函数?如果是因为存储的功能,你可以解决这个问题:

    SET @version := '1';
    
    SELECT CASE val 
               WHEN @version THEN 'SELECT ''Performing upgrade...'''
               ELSE CONCAT('KILL CONNECTION', connection_id())
           END
    INTO   @stmt
    FROM   meta
    WHERE  name = 'Version';
    
    PREPARE stmt FROM @stmt;
    EXECUTE stmt;  -- connection is killed if the version is not correct.
    
    -- remainder of the upgrade script goes here...
    
    • mysql 必须以--skip-reconnect 选项开头,以确保在连接被终止时无法执行任何语句

    我试图解决这个缺点,并将PREPARE 用于其他会阻止当前连接的事情,例如删除用户和撤销他的权限。不幸的是,这并没有按预期工作(不,在FLUSH PRIVILEGES 之后也没有)

    方案#3:使用mysql代理拦截

    另一种解决方案:使用代理。使用 mysql 代理(请参阅:http://forge.mysql.com/wiki/MySQL_Proxy),您可以在 lua 中编写自定义拦截脚本来解释您的自定义命令。使用它,您可以添加必要的控制结构。缺点:你现在必须设计自己的迷你语言,并学习 lua 来解释它:)

    【讨论】:

    • 现在我看到您首先回答使用包装脚本是最省事的。我会走我想的那条路。谢谢。
    • 谢谢你。尽管功能不完善,但我希望您仍然可以完成工作。我也希望它更容易。
    • @RolandBouman,您的“Mysql 代理”链接已关闭。顺便说一句,使用 MySQL“代理”和简单地在应用程序中执行它有什么区别,例如通过 Java 还是 PHP?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-09-16
    • 1970-01-01
    • 2011-11-15
    • 2022-08-23
    • 1970-01-01
    • 1970-01-01
    • 2014-09-01
    相关资源
    最近更新 更多