2017-10-10 79 views
1

目標是在比較MySQL 5.5(引擎MyISAM)中兩個表的模式時添加缺失的列。參數p_table1p_table2將被比較和「同步」的模型表名稱。MySQL同步兩個表的模式的過程

當它被調用時,沒有任何反應,沒有錯誤,沒有任何東西。我試圖記錄一些變量,但它也沒有奏效。

代碼有什麼問題?

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 
+0

你有沒有考慮過使用[mysqldbcompare](https://dev.mysql.com/doc/mysql-utilities/1.5/en/mysqldbcompare.html)而不是自己寫? –

+0

@BillKarwin我不想重寫mysqldbcompare,因爲我沒有db模型來比較。事實上,我正在嘗試針對滯後代碼庫做一些解決方法,並且它不是很好的項目選擇。問題是:爲什麼存儲過程沒有按預期運行?我的意思是,這裏的問題是我無法使程序正常工作,儘管我努力查看文檔,但我不知道發生了什麼。 – susanoobit

回答

1

我發現了一些問題。

首先,您的大部分遊標代碼位於EXCOR HANDLER FOR SQLEXCEPTION中。只有發生錯誤時纔會運行該塊。所以通常這個塊永遠不會運行。

其次,你用CONCAT()列來組成你的ALTER TABLE語句,但是一個或多個列可以是NULL。當你CONCAT()任何帶有null的字符串時,整個concat操作的結果是NULL。所以你必須確保NULL值默認爲非NULL值。

在我的測試中,列的默認值通常是NULL。我們希望這成爲ALTER TABLE語句中的關鍵字「NULL」。此外,如果默認值不爲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。

但即使我的解決方案仍然存在問題:

  • 如果您嘗試沒有默認爲填充表2添加NOT NULL列導致錯誤。
  • 不注意額外存在於第二個表格但不是第一個表格的列。
  • 不同步約束,索引,觸發器,過程或視圖。

完全同步數據庫結構是一項非常複雜的任務。我建議試圖使用MySQL的存儲過程語言來完成這一任務更加困難。

MySQL的存儲過程的執行很糟糕。

  • 文檔不好。
  • 不支持包。
  • 沒有標準程序或豐富的函數庫。
  • 沒有真正的調試器(有些工具嘗試,但他們僞裝它)。
  • 不支持持久化編譯過程。每個使用它們的會話都會重新編譯過程。

我經常推薦給誰習慣於使用的是Oracle或Microsoft SQL Server程序開發人員,以遠離MySQL的存儲過程了。