MySQL用于同步2个表的模式的过程

时间:2017-10-10 19:44:39

标签: mysql stored-procedures synchronization

目标是在比较 MySQL 5.5 (引擎MyISAM)中两个表的模式时添加缺少的列。参数p_table1是模型表名称,p_table2将从中进行比较,并且"同步"。

当它被调用时,没有任何反应,没有错误,没有任何错误。我试图记录一些变量,但它也没有。

代码有什么问题?

CREATE PROCEDURE synchronize_tables(p_table1 VARCHAR(64), p_table2 VARCHAR(64), p_schema_name VARCHAR(64))
BEGIN
    DECLARE v_done INT default false; 
    DECLARE v_actual_column_name VARCHAR(64);
    DECLARE v_does_columns_exist INT default true;
    DECLARE v_column_type LONGTEXT;
    DECLARE v_column_default LONGTEXT;
    DECLARE v_is_nullable VARCHAR(3);

    DECLARE v_cur CURSOR FOR 
        SELECT column_name 
        FROM INFORMATION_SCHEMA.COLUMNS 
        WHERE table_name = p_table1 
        AND table_schema = p_schema_name;

    DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_done = TRUE;
    DECLARE EXIT HANDLER FOR SQLEXCEPTION
    BEGIN
        OPEN v_cur;

        read_loop: LOOP
            FETCH v_cur INTO v_actual_column_name;
            IF v_done THEN
                LEAVE read_loop;
            END IF;

            SELECT count(*) INTO v_does_columns_exist
            FROM INFORMATION_SCHEMA.COLUMNS
            WHERE table_name = p_table2
            AND table_schema = p_schema_name
            AND column_name = v_actual_column_name;

            IF NOT v_does_columns_exist THEN
                SELECT column_type, COLUMN_DEFAULT, IS_NULLABLE
                INTO v_column_type, v_column_default, v_is_nullable
                FROM INFORMATION_SCHEMA.COLUMNS
                WHERE table_name = p_table1
                AND table_schema = p_schema_name
                AND column_name = v_actual_column_name;

                SET @stmt_text = CONCAT('ALTER TABLE ', p_schema_name, '.', p_table2,
                    ' ADD COLUMN ', v_actual_column_name, ' ', v_column_type, ' ', IF(upper(v_is_nullable) = 'NO', 'NOT NULL', ''),
                    ' DEFAULT ', v_column_default);

                prepare v_stmt FROM @stmt_text;
                execute v_stmt;
                deallocate prepare v_stmt;
            END IF;
        END LOOP;

        CLOSE v_cur;
    END;
END

1 个答案:

答案 0 :(得分:1)

我发现了一些问题。

首先,您的大多数游标代码都在EXIT HANDLER FOR SQLEXCEPTION中。仅在发生错误时才运行此块。所以通常这个块永远不会运行。

其次,您使用CONCAT()列来形成ALTER TABLE语句,但是一个或多个列可以为NULL。当您CONCAT()任何具有null的字符串时,整个concat操作的结果为NULL。所以你必须确保NULL默认为非NULL。

在我的测试中,列默认值通常为NULL。我们希望这成为关键字" NULL"在ALTER TABLE语句中。此外,如果默认值不是NULL,您可能想引用它,因为普通的默认值可能是字符串或日期,并且您不能引用它。解决方案:QUOTE()是一个内置函数,可以正确引用字符串,甚至可以将NULL转换为关键字" NULL"。

这就是我的工作:

CREATE PROCEDURE synchronize_tables(p_table1 VARCHAR(64), 
    p_table2 VARCHAR(64), p_schema_name VARCHAR(64))
BEGIN
    DECLARE v_done INT default false;
    DECLARE v_actual_column_name VARCHAR(64);
    DECLARE v_does_columns_exist INT default true;
    DECLARE v_column_type LONGTEXT;
    DECLARE v_column_default LONGTEXT;
    DECLARE v_is_nullable VARCHAR(3);

    DECLARE v_cur CURSOR FOR
        SELECT column_name
        FROM INFORMATION_SCHEMA.COLUMNS
        WHERE table_name = p_table1
        AND table_schema = p_schema_name;

    DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_done = TRUE;

    OPEN v_cur;

    read_loop: LOOP
        FETCH v_cur INTO v_actual_column_name;
        IF v_done THEN
            LEAVE read_loop;
        END IF; 

        SELECT count(*) INTO v_does_columns_exist
        FROM INFORMATION_SCHEMA.COLUMNS
        WHERE table_name = p_table2
        AND table_schema = p_schema_name
        AND column_name = v_actual_column_name;

        IF NOT v_does_columns_exist THEN
            SELECT column_type, COLUMN_DEFAULT, IS_NULLABLE
            INTO v_column_type, v_column_default, v_is_nullable
            FROM INFORMATION_SCHEMA.COLUMNS
            WHERE table_name = p_table1
            AND table_schema = p_schema_name
            AND column_name = v_actual_column_name;

            SET @stmt_text = CONCAT('ALTER TABLE ', 
                p_schema_name, '.', p_table2,
                ' ADD COLUMN ', v_actual_column_name, ' ',
                v_column_type, ' ', 
                IF(upper(v_is_nullable) = 'NO', 'NOT NULL', ''),
                ' DEFAULT ', QUOTE(v_column_default));
            PREPARE v_stmt FROM @stmt_text;
            EXECUTE v_stmt;
            DEALLOCATE prepare v_stmt;
        END IF; 
    END LOOP;

    CLOSE v_cur;
END

这种方法还有其他问题:

  • 标识符未分隔。
  • 它为每个缺失的列生成一个ALTER TABLE,即使可以在一个ALTER中添加多个列。
  • 当列为非NULL但没有默认值时,它没有做正确的事。

但是,无论如何,我不会用游标做到这一点。有一种更简单的方法:

CREATE PROCEDURE synchronize_tables(p_table1 VARCHAR(64), 
    p_table2 VARCHAR(64), p_schema_name VARCHAR(64))
BEGIN
    SELECT CONCAT(
      'ALTER TABLE `', C1.TABLE_SCHEMA, '`.`', p_table2, '` ',
      GROUP_CONCAT(CONCAT(
        'ADD COLUMN `', C1.COLUMN_NAME, '` ', C1.COLUMN_TYPE,
        IF(C1.IS_NULLABLE='NO', ' NOT NULL ', ''),
        IF(C1.COLUMN_DEFAULT IS NULL, '', 
          CONCAT(' DEFAULT ', QUOTE(C1.COLUMN_DEFAULT)))
        ) SEPARATOR ', '
      )
    ) INTO @stmt_text
    FROM INFORMATION_SCHEMA.COLUMNS AS C1
    LEFT OUTER JOIN INFORMATION_SCHEMA.COLUMNS AS C2
      ON C1.TABLE_SCHEMA=C2.TABLE_SCHEMA 
      AND C2.TABLE_NAME=p_table2 
      AND C1.COLUMN_NAME=C2.COLUMN_NAME
    WHERE C1.TABLE_SCHEMA=p_schema_name AND C1.TABLE_NAME=p_table1
      AND C2.TABLE_SCHEMA IS NULL;

    PREPARE v_stmt FROM @stmt_text;
    EXECUTE v_stmt;
    DEALLOCATE prepare v_stmt;
END

这使一个 ALTER TABLE添加所有列。它划分标识符。它更好地处理NOT NULL。

但即便是我的解决方案仍有问题:

  • 如果尝试向填充的table2添加没有DEFAULT的NOT NULL列,则会导致错误。
  • 没有注意到第二个表中存在的额外列,而不是第一个表。
  • 不同步约束,索引,触发器,过程或视图。

完全同步数据库结构是一项非常复杂的任务。我建议尝试使用MySQL的存储过程语言更难以完成一项艰巨的任务。

MySQL对存储过程的实现很糟糕。

  • 文件很差。
  • 不支持包裹。
  • 没有标准程序或丰富的函数库。
  • 不存在真正的调试器(有些工具可以尝试,但是他们会伪造它)。
  • 不支持持久编译程序。每个使用它们的会话都会重新编译程序。

我经常向习惯使用Oracle或Microsoft SQL Server程序的开发人员推荐远离MySQL存储过程。