【问题标题】:Force InnoDB to recheck foreign keys on a table/tables?强制 InnoDB 重新检查表上的外键?
【发布时间】:2011-01-16 02:02:40
【问题描述】:

我有一组InnoDB 表,我需要通过删除一些行并插入其他行来定期维护这些表。一些表具有引用其他表的外键约束,因此这意味着表加载顺序很重要。为了插入新行而不用担心表的顺序,我使用:

SET FOREIGN_KEY_CHECKS=0;

之前,然后:

SET FOREIGN_KEY_CHECKS=1;

之后。

加载完成后,我想检查更新表中的数据是否仍然保持引用完整性——新行不会破坏外键约束——但似乎没有办法这个。

作为测试,我输入了我确信违反了外键约束的数据,并且在重新启用外键检查后,mysql 没有产生任何警告或错误。

如果我试图找到一种方法来指定表加载顺序,并在加载过程中保留外键检查,这将不允许我将数据加载到具有自引用外键约束的表中,所以这不是一个可接受的解决方案。

有没有办法强制 InnoDB 验证表或数据库的外键约束?

【问题讨论】:

  • 当您通过SET FOREIGN_KEY_CHECKS=1; 重新启用 FK 时,MySQL 不执行 FK 验证,这是一种讽刺,令人难以置信。至少,它们应该公开内置功能,这样我们就不必费劲儿地验证数据库中数据的完整性。
  • 在 sqlite(不是 mysql)中仅供参考,您可以运行 PRAGMA schema.foreign_key_check;

标签: mysql foreign-keys innodb


【解决方案1】:

我修改了脚本来处理多列外键。

DELIMITER $$

DROP PROCEDURE IF EXISTS ANALYZE_INVALID_FOREIGN_KEYS$$

CREATE PROCEDURE `ANALYZE_INVALID_FOREIGN_KEYS`(
    IN `checked_database_name` VARCHAR(64),
    IN `checked_table_name` VARCHAR(64),
    IN `temporary_result_table` ENUM('Y', 'N')
)
    LANGUAGE SQL
    NOT DETERMINISTIC
    READS SQL DATA

BEGIN
    DECLARE TABLE_SCHEMA_VAR VARCHAR(64);
    DECLARE TABLE_NAME_VAR VARCHAR(64);
    DECLARE COLUMN_NAME_VAR VARCHAR(64); 
    DECLARE CONSTRAINT_NAME_VAR VARCHAR(64);
    DECLARE REFERENCED_TABLE_SCHEMA_VAR VARCHAR(64);
    DECLARE REFERENCED_TABLE_NAME_VAR VARCHAR(64);
    DECLARE REFERENCED_COLUMN_NAME_VAR VARCHAR(64);

    DECLARE done INT DEFAULT 0;

    DECLARE foreign_key_cursor CURSOR FOR
        SELECT
            `TABLE_SCHEMA`,
            `TABLE_NAME`,
            `COLUMN_NAME`,
            `CONSTRAINT_NAME`,
            `REFERENCED_TABLE_SCHEMA`,
            `REFERENCED_TABLE_NAME`,
            `REFERENCED_COLUMN_NAME`
        FROM 
            information_schema.KEY_COLUMN_USAGE 
        WHERE 
            `CONSTRAINT_SCHEMA` LIKE checked_database_name AND
            `TABLE_NAME` LIKE checked_table_name AND
            `REFERENCED_TABLE_SCHEMA` IS NOT NULL;

    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;

    IF temporary_result_table = 'N' THEN
        DROP TEMPORARY TABLE IF EXISTS INVALID_FOREIGN_KEYS;
        DROP TABLE IF EXISTS INVALID_FOREIGN_KEYS;

        CREATE TABLE INVALID_FOREIGN_KEYS(
            `TABLE_SCHEMA` VARCHAR(64), 
            `TABLE_NAME` VARCHAR(64), 
            `COLUMN_NAME` VARCHAR(64), 
            `CONSTRAINT_NAME` VARCHAR(64),
            `REFERENCED_TABLE_SCHEMA` VARCHAR(64),
            `REFERENCED_TABLE_NAME` VARCHAR(64),
            `REFERENCED_COLUMN_NAME` VARCHAR(64),
            `INVALID_KEY_COUNT` INT,
            `INVALID_KEY_SQL` VARCHAR(1024)
        );
    ELSEIF temporary_result_table = 'Y' THEN
        DROP TEMPORARY TABLE IF EXISTS INVALID_FOREIGN_KEYS;
        DROP TABLE IF EXISTS INVALID_FOREIGN_KEYS;

        CREATE TEMPORARY TABLE INVALID_FOREIGN_KEYS(
            `TABLE_SCHEMA` VARCHAR(64), 
            `TABLE_NAME` VARCHAR(64), 
            `COLUMN_NAME` VARCHAR(64), 
            `CONSTRAINT_NAME` VARCHAR(64),
            `REFERENCED_TABLE_SCHEMA` VARCHAR(64),
            `REFERENCED_TABLE_NAME` VARCHAR(64),
            `REFERENCED_COLUMN_NAME` VARCHAR(64),
            `INVALID_KEY_COUNT` INT,
            `INVALID_KEY_SQL` VARCHAR(1024)
        );
    END IF;

    SET @prev_constraint_name = '';
    SET @prev_table_schema = '';
    SET @prev_table_name = '';
    SET @prev_referenced_table_schema = '';
    SET @prev_referenced_table_name = '';

    SET @from_part = '';
    SET @where_part = '';
    SET @where_nullable = '';

    SET @all_columns = '';
    SET @all_referenced_columns = '';

    OPEN foreign_key_cursor;
    foreign_key_cursor_loop: LOOP               
        FETCH foreign_key_cursor INTO 
        TABLE_SCHEMA_VAR, 
        TABLE_NAME_VAR, 
        COLUMN_NAME_VAR, 
        CONSTRAINT_NAME_VAR, 
        REFERENCED_TABLE_SCHEMA_VAR, 
        REFERENCED_TABLE_NAME_VAR, 
        REFERENCED_COLUMN_NAME_VAR;

        IF done THEN
            LEAVE foreign_key_cursor_loop;
        END IF;

        IF (@prev_constraint_name <> CONSTRAINT_NAME_VAR AND @from_part <> '' AND @where_part <> '') THEN

            SET @full_query = CONCAT('SELECT COUNT(*) ', @from_part, ' WHERE (', @where_nullable , ') AND ', @from_where_part, 'WHERE ', @where_part, ') INTO @invalid_key_count;');
            SET @invalid_query = CONCAT('SELECT * ', @from_part, ' WHERE (', @where_nullable , ') AND ', @from_where_part, 'WHERE ', @where_part, ')');
            PREPARE stmt FROM @full_query;

            EXECUTE stmt;
            IF @invalid_key_count > 0 THEN
                INSERT INTO 
                    INVALID_FOREIGN_KEYS 
                SET 
                    `TABLE_SCHEMA` = @prev_table_schema, 
                    `TABLE_NAME` = @prev_table_name, 
                    `COLUMN_NAME` = @all_columns, 
                    `CONSTRAINT_NAME` = @prev_constraint_name, 
                    `REFERENCED_TABLE_SCHEMA` = @prev_referenced_table_schema, 
                    `REFERENCED_TABLE_NAME` = @prev_table_name, 
                    `REFERENCED_COLUMN_NAME` = @all_referenced_columns, 
                    `INVALID_KEY_COUNT` = @invalid_key_count,
                    `INVALID_KEY_SQL` = @invalid_query;
            END IF;
            DEALLOCATE PREPARE stmt; 

            SET @where_part = '';
            SET @where_nullable = '';

            SET @all_columns = '';
            SET @all_referenced_columns = '';
        END IF;

        IF (LENGTH(@where_part) > 0) THEN
            SET @where_nullable = CONCAT(@where_nullable, ' OR ');
            SET @where_part = CONCAT(@where_part, ' AND ');

            SET @all_columns = CONCAT(@all_columns, ', ' COLUMN_NAME_VAR);
            SET @all_referenced_columns = CONCAT(@all_referenced_columns, ', ' REFERENCED_COLUMN_NAME_VAR);
        ELSE
            SET @from_part = CONCAT('FROM ', '`', TABLE_SCHEMA_VAR, '`.`', TABLE_NAME_VAR, '`', ' AS REFERRING ');
            SET @from_where_part = CONCAT('NOT EXISTS (SELECT * FROM `', REFERENCED_TABLE_SCHEMA_VAR, '`.`', REFERENCED_TABLE_NAME_VAR, '`', ' AS REFERRED ');

            SET @all_columns = COLUMN_NAME_VAR;
            SET @all_referenced_columns = REFERENCED_COLUMN_NAME_VAR;
        END IF;

        SET @where_nullable = CONCAT(@where_nullable, 'REFERRING.', COLUMN_NAME_VAR, ' IS NOT NULL');
        SET @where_part = CONCAT(@where_part, 'REFERRING.', COLUMN_NAME_VAR, ' = ', 'REFERRED.', REFERENCED_COLUMN_NAME_VAR);

        SET @prev_constraint_name = CONSTRAINT_NAME_VAR;
        SET @prev_table_schema = TABLE_SCHEMA_VAR;
        SET @prev_table_name = TABLE_NAME_VAR;
        SET @prev_referenced_table_schema = REFERENCED_TABLE_SCHEMA_VAR;
        SET @prev_referenced_table_name = REFERENCED_TABLE_NAME_VAR;

    END LOOP foreign_key_cursor_loop;

    IF (@where_part <> '' AND @from_part <> '') THEN

        SET @full_query = CONCAT('SELECT COUNT(*) ', @from_part, ' WHERE (', @where_nullable , ') AND ', @from_where_part, 'WHERE ', @where_part, ') INTO @invalid_key_count;');
        SET @invalid_query = CONCAT('SELECT * ', @from_part, ' WHERE (', @where_nullable , ') AND ', @from_where_part, 'WHERE ', @where_part, ')');
        PREPARE stmt FROM @full_query;

        EXECUTE stmt;
        IF @invalid_key_count > 0 THEN
            INSERT INTO 
                INVALID_FOREIGN_KEYS 
            SET 
                `TABLE_SCHEMA` = @prev_table_schema, 
                `TABLE_NAME` = @prev_table_name, 
                `COLUMN_NAME` = @all_columns, 
                `CONSTRAINT_NAME` = @prev_constraint_name, 
                `REFERENCED_TABLE_SCHEMA` = @prev_referenced_table_schema, 
                `REFERENCED_TABLE_NAME` = @prev_table_name, 
                `REFERENCED_COLUMN_NAME` = @all_referenced_columns, 
                `INVALID_KEY_COUNT` = @invalid_key_count,
                `INVALID_KEY_SQL` = @invalid_query;
        END IF;
        DEALLOCATE PREPARE stmt; 
    END IF;
END$$

DELIMITER ;

CALL ANALYZE_INVALID_FOREIGN_KEYS('%', '%', 'Y');
DROP PROCEDURE IF EXISTS ANALYZE_INVALID_FOREIGN_KEYS;

SELECT * FROM INVALID_FOREIGN_KEYS;

【讨论】:

  • @Rayman @nailujed 我收到Incorrect parameters in the call to native function 'CONCAT' 错误。可能SET @all_referenced_columns = CONCAT(@all_referenced_columns, ', ' REFERENCED_COLUMN_NAME_VAR);CONCAT(@all_columns, ', ' COLUMN_NAME_VAR); 行不正确。您是否忘记在单独的参数中添加逗号?
【解决方案2】:

没有工具可以做到这一点。但是您可以编写一个脚本,该脚本将遍历您的所有表,删除并重新创建外键约束。游戏中,如果有问题,就会报错。

【讨论】:

    【解决方案3】:

    相同的检查,但针对无效的唯一键分析:

    --> 小错误/功能:它也会报告重复的空值。 (而 mysql 允许重复的空值)。

    DELIMITER $$
    
    DROP PROCEDURE IF EXISTS ANALYZE_INVALID_UNIQUE_KEYS$$
    
    CREATE
        PROCEDURE `ANALYZE_INVALID_UNIQUE_KEYS`(
            checked_database_name VARCHAR(64), 
            checked_table_name VARCHAR(64))
    
        LANGUAGE SQL
        NOT DETERMINISTIC
        READS SQL DATA
    
        BEGIN
            DECLARE TABLE_SCHEMA_VAR VARCHAR(64);
            DECLARE TABLE_NAME_VAR VARCHAR(64);
            DECLARE COLUMN_NAMES_VAR VARCHAR(1000); 
            DECLARE CONSTRAINT_NAME_VAR VARCHAR(64);
    
            DECLARE done INT DEFAULT 0;
    
            DECLARE unique_key_cursor CURSOR FOR
                select kcu.table_schema sch,
                       kcu.table_name tbl, 
                       group_concat(kcu.column_name) colName, 
                       kcu.constraint_name constName
                from 
                    information_schema.table_constraints tc
                join 
                    information_schema.key_column_usage kcu 
                on 
                    kcu.constraint_name=tc.constraint_name 
                    and kcu.constraint_schema=tc.constraint_schema
                    and kcu.table_name=tc.table_name  
                where 
                    kcu.table_schema like checked_database_name 
                    and kcu.table_name like checked_table_name 
                    and tc.constraint_type="UNIQUE" group by sch, tbl, constName;
    
            DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
    
            DROP TEMPORARY TABLE IF EXISTS INVALID_UNIQUE_KEYS;
            CREATE TEMPORARY TABLE INVALID_UNIQUE_KEYS(
                `TABLE_SCHEMA` VARCHAR(64), 
                `TABLE_NAME` VARCHAR(64), 
                `COLUMN_NAMES` VARCHAR(1000), 
                `CONSTRAINT_NAME` VARCHAR(64),
                `INVALID_KEY_COUNT` INT
            );
    
    
    
            OPEN unique_key_cursor;
            unique_key_cursor_loop: LOOP
                FETCH unique_key_cursor INTO 
                TABLE_SCHEMA_VAR, 
                TABLE_NAME_VAR, 
                COLUMN_NAMES_VAR, 
                CONSTRAINT_NAME_VAR;
                IF done THEN
                    LEAVE unique_key_cursor_loop;
                END IF;
    
                SET @from_part = CONCAT('FROM (SELECT COUNT(*) counter FROM', '`', TABLE_SCHEMA_VAR, '`.`', TABLE_NAME_VAR, '`', 
                        ' GROUP BY ', COLUMN_NAMES_VAR , ') as s where s.counter > 1');
                SET @full_query = CONCAT('SELECT COUNT(*) ', @from_part, ' INTO @invalid_key_count;');
                PREPARE stmt FROM @full_query;
                EXECUTE stmt;
                IF @invalid_key_count > 0 THEN
                    INSERT INTO 
                        INVALID_UNIQUE_KEYS 
                    SET 
                        `TABLE_SCHEMA` = TABLE_SCHEMA_VAR, 
                        `TABLE_NAME` = TABLE_NAME_VAR, 
                        `COLUMN_NAMES` = COLUMN_NAMES_VAR, 
                        `CONSTRAINT_NAME` = CONSTRAINT_NAME_VAR, 
                        `INVALID_KEY_COUNT` = @invalid_key_count;
                END IF;
                DEALLOCATE PREPARE stmt; 
    
            END LOOP unique_key_cursor_loop;
        END$$
    
    DELIMITER ;
    
    CALL ANALYZE_INVALID_UNIQUE_KEYS('%', '%');
    DROP PROCEDURE IF EXISTS ANALYZE_INVALID_UNIQUE_KEYS;
    
    SELECT * FROM INVALID_UNIQUE_KEYS;
    

    【讨论】:

      【解决方案4】:
      DELIMITER $$
      
      DROP PROCEDURE IF EXISTS ANALYZE_INVALID_FOREIGN_KEYS$$
      
      CREATE
          PROCEDURE `ANALYZE_INVALID_FOREIGN_KEYS`(
              checked_database_name VARCHAR(64), 
              checked_table_name VARCHAR(64), 
              temporary_result_table ENUM('Y', 'N'))
      
          LANGUAGE SQL
          NOT DETERMINISTIC
          READS SQL DATA
      
          BEGIN
              DECLARE TABLE_SCHEMA_VAR VARCHAR(64);
              DECLARE TABLE_NAME_VAR VARCHAR(64);
              DECLARE COLUMN_NAME_VAR VARCHAR(64); 
              DECLARE CONSTRAINT_NAME_VAR VARCHAR(64);
              DECLARE REFERENCED_TABLE_SCHEMA_VAR VARCHAR(64);
              DECLARE REFERENCED_TABLE_NAME_VAR VARCHAR(64);
              DECLARE REFERENCED_COLUMN_NAME_VAR VARCHAR(64);
              DECLARE KEYS_SQL_VAR VARCHAR(1024);
      
              DECLARE done INT DEFAULT 0;
      
              DECLARE foreign_key_cursor CURSOR FOR
                  SELECT
                      `TABLE_SCHEMA`,
                      `TABLE_NAME`,
                      `COLUMN_NAME`,
                      `CONSTRAINT_NAME`,
                      `REFERENCED_TABLE_SCHEMA`,
                      `REFERENCED_TABLE_NAME`,
                      `REFERENCED_COLUMN_NAME`
                  FROM 
                      information_schema.KEY_COLUMN_USAGE 
                  WHERE 
                      `CONSTRAINT_SCHEMA` LIKE checked_database_name AND
                      `TABLE_NAME` LIKE checked_table_name AND
                      `REFERENCED_TABLE_SCHEMA` IS NOT NULL;
      
              DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
      
              IF temporary_result_table = 'N' THEN
                  DROP TEMPORARY TABLE IF EXISTS INVALID_FOREIGN_KEYS;
                  DROP TABLE IF EXISTS INVALID_FOREIGN_KEYS;
      
                  CREATE TABLE INVALID_FOREIGN_KEYS(
                      `TABLE_SCHEMA` VARCHAR(64), 
                      `TABLE_NAME` VARCHAR(64), 
                      `COLUMN_NAME` VARCHAR(64), 
                      `CONSTRAINT_NAME` VARCHAR(64),
                      `REFERENCED_TABLE_SCHEMA` VARCHAR(64),
                      `REFERENCED_TABLE_NAME` VARCHAR(64),
                      `REFERENCED_COLUMN_NAME` VARCHAR(64),
                      `INVALID_KEY_COUNT` INT,
                      `INVALID_KEY_SQL` VARCHAR(1024)
                  );
              ELSEIF temporary_result_table = 'Y' THEN
                  DROP TEMPORARY TABLE IF EXISTS INVALID_FOREIGN_KEYS;
                  DROP TABLE IF EXISTS INVALID_FOREIGN_KEYS;
      
                  CREATE TEMPORARY TABLE INVALID_FOREIGN_KEYS(
                      `TABLE_SCHEMA` VARCHAR(64), 
                      `TABLE_NAME` VARCHAR(64), 
                      `COLUMN_NAME` VARCHAR(64), 
                      `CONSTRAINT_NAME` VARCHAR(64),
                      `REFERENCED_TABLE_SCHEMA` VARCHAR(64),
                      `REFERENCED_TABLE_NAME` VARCHAR(64),
                      `REFERENCED_COLUMN_NAME` VARCHAR(64),
                      `INVALID_KEY_COUNT` INT,
                      `INVALID_KEY_SQL` VARCHAR(1024)
                  );
              END IF;
      
      
              OPEN foreign_key_cursor;
              foreign_key_cursor_loop: LOOP
                  FETCH foreign_key_cursor INTO 
                  TABLE_SCHEMA_VAR, 
                  TABLE_NAME_VAR, 
                  COLUMN_NAME_VAR, 
                  CONSTRAINT_NAME_VAR, 
                  REFERENCED_TABLE_SCHEMA_VAR, 
                  REFERENCED_TABLE_NAME_VAR, 
                  REFERENCED_COLUMN_NAME_VAR;
                  IF done THEN
                      LEAVE foreign_key_cursor_loop;
                  END IF;
      
      
                  SET @from_part = CONCAT('FROM ', '`', TABLE_SCHEMA_VAR, '`.`', TABLE_NAME_VAR, '`', ' AS REFERRING ', 
                       'LEFT JOIN `', REFERENCED_TABLE_SCHEMA_VAR, '`.`', REFERENCED_TABLE_NAME_VAR, '`', ' AS REFERRED ', 
                       'ON (REFERRING', '.`', COLUMN_NAME_VAR, '`', ' = ', 'REFERRED', '.`', REFERENCED_COLUMN_NAME_VAR, '`', ') ', 
                       'WHERE REFERRING', '.`', COLUMN_NAME_VAR, '`', ' IS NOT NULL ',
                       'AND REFERRED', '.`', REFERENCED_COLUMN_NAME_VAR, '`', ' IS NULL');
                  SET @full_query = CONCAT('SELECT COUNT(*) ', @from_part, ' INTO @invalid_key_count;');
                  PREPARE stmt FROM @full_query;
      
                  EXECUTE stmt;
                  IF @invalid_key_count > 0 THEN
                      INSERT INTO 
                          INVALID_FOREIGN_KEYS 
                      SET 
                          `TABLE_SCHEMA` = TABLE_SCHEMA_VAR, 
                          `TABLE_NAME` = TABLE_NAME_VAR, 
                          `COLUMN_NAME` = COLUMN_NAME_VAR, 
                          `CONSTRAINT_NAME` = CONSTRAINT_NAME_VAR, 
                          `REFERENCED_TABLE_SCHEMA` = REFERENCED_TABLE_SCHEMA_VAR, 
                          `REFERENCED_TABLE_NAME` = REFERENCED_TABLE_NAME_VAR, 
                          `REFERENCED_COLUMN_NAME` = REFERENCED_COLUMN_NAME_VAR, 
                          `INVALID_KEY_COUNT` = @invalid_key_count,
                          `INVALID_KEY_SQL` = CONCAT('SELECT ', 
                              'REFERRING.', '`', COLUMN_NAME_VAR, '` ', 'AS "Invalid: ', COLUMN_NAME_VAR, '", ', 
                              'REFERRING.* ', 
                              @from_part, ';');
                  END IF;
                  DEALLOCATE PREPARE stmt; 
      
              END LOOP foreign_key_cursor_loop;
          END$$
      
      DELIMITER ;
      
      CALL ANALYZE_INVALID_FOREIGN_KEYS('%', '%', 'Y');
      DROP PROCEDURE IF EXISTS ANALYZE_INVALID_FOREIGN_KEYS;
      
      SELECT * FROM INVALID_FOREIGN_KEYS;
      

      您可以使用此存储过程检查所有数据库中的无效外键。 结果将被加载到INVALID_FOREIGN_KEYS 表中。 ANALYZE_INVALID_FOREIGN_KEYS的参数:

      1. 数据库名称模式(LIKE 样式)
      2. 表格名称模式(LIKE 样式)
      3. 结果是否是暂时的。可以是:'Y''N'NULL

        • 如果是'Y'ANALYZE_INVALID_FOREIGN_KEYS 结果表将是临时表。 临时表对其他会话不可见。 您可以使用临时结果表并行执行多个ANALYZE_INVALID_FOREIGN_KEYS(...) 存储过程。
        • 但是如果您对其他会话的部分结果感兴趣,那么您必须使用'N',然后从其他会话执行SELECT * FROM INVALID_FOREIGN_KEYS;
        • 必须使用NULL在事务中跳过创建结果表,因为MySQL对CREATE TABLE ...DROP TABLE ...在事务中执行隐式提交,所以创建结果表会导致事务出现问题。在这种情况下,您必须自己从BEGIN; COMMIT/ROLLBACK; 块中创建结果表:

          CREATE TABLE INVALID_FOREIGN_KEYS(
              `TABLE_SCHEMA` VARCHAR(64), 
              `TABLE_NAME` VARCHAR(64), 
              `COLUMN_NAME` VARCHAR(64), 
              `CONSTRAINT_NAME` VARCHAR(64),
              `REFERENCED_TABLE_SCHEMA` VARCHAR(64),
              `REFERENCED_TABLE_NAME` VARCHAR(64),
              `REFERENCED_COLUMN_NAME` VARCHAR(64),
              `INVALID_KEY_COUNT` INT,
              `INVALID_KEY_SQL` VARCHAR(1024)
          );
          

          访问 MySQL 网站了解隐式提交:http://dev.mysql.com/doc/refman/5.6/en/implicit-commit.html

      INVALID_FOREIGN_KEYS 行将仅包含无效数据库、表、列的名称。但是如果有的话,你可以看到无效的引用行,执行值为INVALID_FOREIGN_KEYSINVALID_KEY_SQL 列。

      如果引用列(也称为外部索引)和引用列(通常是主键)上有索引,则此存储过程将非常快。

      【讨论】:

      • 很好的答案。欢迎来到 Stack Overflow!
      • 我发现我必须在 cursors where 子句中的 checked_* 参数之后添加“collat​​e utf8_general_ci”,因为我的信息架构使用 utf8_general_ci,而登录帐户使用更正确的 utf8_unicode_ci。如果没有这个,我在 like 子句上收到了排序规则不匹配错误。 “在哪里 CONSTRAINT_SCHEMA LIKE checked_database_name collat​​e utf8_general_ci 和 TABLE_NAME LIKE checked_table_name collat​​e utf8_general_ci AND REFERENCED_TABLE_SCHEMA 不为空;
      • @ClearCrescendo 可以通过在双方强制使用相同的排序规则来消除 LIKE 排序错误,例如:(CONSTRAINT_SCHEMA COLLATE utf8_unicode_ci) LIKE (checked_database_name COLLATE utf8_unicode_ci)
      • 请注意,可以将 AND REFERENCED_TABLE_NAME = 'name_of_the_table_that_one_wants_to_check_children_for' 添加到 foreign_key_cursor WHERE 以仅检查单个表的子 FK。
      【解决方案5】:

      感谢您的出色回答 - 这是一个非常方便的工具。这是该过程的略微修改版本,其中在输出表中包含 SQL 以删除具有无效键的键 - 对于您已确认这些行只是缺少/禁用删除级联规则的孤儿(而不是主要的孤儿)的情况很方便关键更改或其他更复杂的情况)。

      DELIMITER $$
      
      DROP PROCEDURE IF EXISTS ANALYZE_INVALID_FOREIGN_KEYS$$
      
      CREATE
          PROCEDURE `ANALYZE_INVALID_FOREIGN_KEYS`(
              checked_database_name VARCHAR(64), 
              checked_table_name VARCHAR(64), 
              temporary_result_table ENUM('Y', 'N'))
      
          LANGUAGE SQL
          NOT DETERMINISTIC
          READS SQL DATA
      
          BEGIN
              DECLARE TABLE_SCHEMA_VAR VARCHAR(64);
              DECLARE TABLE_NAME_VAR VARCHAR(64);
              DECLARE COLUMN_NAME_VAR VARCHAR(64); 
              DECLARE CONSTRAINT_NAME_VAR VARCHAR(64);
              DECLARE REFERENCED_TABLE_SCHEMA_VAR VARCHAR(64);
              DECLARE REFERENCED_TABLE_NAME_VAR VARCHAR(64);
              DECLARE REFERENCED_COLUMN_NAME_VAR VARCHAR(64);
              DECLARE KEYS_SQL_VAR VARCHAR(1024);
      
              DECLARE done INT DEFAULT 0;
      
              DECLARE foreign_key_cursor CURSOR FOR
                  SELECT
                      `TABLE_SCHEMA`,
                      `TABLE_NAME`,
                      `COLUMN_NAME`,
                      `CONSTRAINT_NAME`,
                      `REFERENCED_TABLE_SCHEMA`,
                      `REFERENCED_TABLE_NAME`,
                      `REFERENCED_COLUMN_NAME`
                  FROM 
                      information_schema.KEY_COLUMN_USAGE 
                  WHERE 
                      `CONSTRAINT_SCHEMA` LIKE checked_database_name AND
                      `TABLE_NAME` LIKE checked_table_name AND
                      `REFERENCED_TABLE_SCHEMA` IS NOT NULL;
      
              DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
      
              IF temporary_result_table = 'N' THEN
                  DROP TEMPORARY TABLE IF EXISTS INVALID_FOREIGN_KEYS;
                  DROP TABLE IF EXISTS INVALID_FOREIGN_KEYS;
      
                  CREATE TABLE INVALID_FOREIGN_KEYS(
                      `TABLE_SCHEMA` VARCHAR(64), 
                      `TABLE_NAME` VARCHAR(64), 
                      `COLUMN_NAME` VARCHAR(64), 
                      `CONSTRAINT_NAME` VARCHAR(64),
                      `REFERENCED_TABLE_SCHEMA` VARCHAR(64),
                      `REFERENCED_TABLE_NAME` VARCHAR(64),
                      `REFERENCED_COLUMN_NAME` VARCHAR(64),
                      `INVALID_KEY_COUNT` INT,
                      `INVALID_KEY_SQL` VARCHAR(1024),
                      `INVALID_KEY_DELETE_SQL` VARCHAR(1024)
                  );
              ELSEIF temporary_result_table = 'Y' THEN
                  DROP TEMPORARY TABLE IF EXISTS INVALID_FOREIGN_KEYS;
                  DROP TABLE IF EXISTS INVALID_FOREIGN_KEYS;
      
                  CREATE TEMPORARY TABLE INVALID_FOREIGN_KEYS(
                      `TABLE_SCHEMA` VARCHAR(64), 
                      `TABLE_NAME` VARCHAR(64), 
                      `COLUMN_NAME` VARCHAR(64), 
                      `CONSTRAINT_NAME` VARCHAR(64),
                      `REFERENCED_TABLE_SCHEMA` VARCHAR(64),
                      `REFERENCED_TABLE_NAME` VARCHAR(64),
                      `REFERENCED_COLUMN_NAME` VARCHAR(64),
                      `INVALID_KEY_COUNT` INT,
                      `INVALID_KEY_SQL` VARCHAR(1024),
                      `INVALID_KEY_DELETE_SQL` VARCHAR(1024)
                  );
              END IF;
      
      
              OPEN foreign_key_cursor;
              foreign_key_cursor_loop: LOOP
                  FETCH foreign_key_cursor INTO 
                  TABLE_SCHEMA_VAR, 
                  TABLE_NAME_VAR, 
                  COLUMN_NAME_VAR, 
                  CONSTRAINT_NAME_VAR, 
                  REFERENCED_TABLE_SCHEMA_VAR, 
                  REFERENCED_TABLE_NAME_VAR, 
                  REFERENCED_COLUMN_NAME_VAR;
                  IF done THEN
                      LEAVE foreign_key_cursor_loop;
                  END IF;
      
      
                  SET @from_part = CONCAT('FROM ', '`', TABLE_SCHEMA_VAR, '`.`', TABLE_NAME_VAR, '`', ' AS REFERRING ', 
                       'LEFT JOIN `', REFERENCED_TABLE_SCHEMA_VAR, '`.`', REFERENCED_TABLE_NAME_VAR, '`', ' AS REFERRED ', 
                       'ON (REFERRING', '.`', COLUMN_NAME_VAR, '`', ' = ', 'REFERRED', '.`', REFERENCED_COLUMN_NAME_VAR, '`', ') ', 
                       'WHERE REFERRING', '.`', COLUMN_NAME_VAR, '`', ' IS NOT NULL ',
                       'AND REFERRED', '.`', REFERENCED_COLUMN_NAME_VAR, '`', ' IS NULL');
                  SET @full_query = CONCAT('SELECT COUNT(*) ', @from_part, ' INTO @invalid_key_count;');
                  PREPARE stmt FROM @full_query;
      
                  EXECUTE stmt;
                  IF @invalid_key_count > 0 THEN
                      INSERT INTO 
                          INVALID_FOREIGN_KEYS 
                      SET 
                          `TABLE_SCHEMA` = TABLE_SCHEMA_VAR, 
                          `TABLE_NAME` = TABLE_NAME_VAR, 
                          `COLUMN_NAME` = COLUMN_NAME_VAR, 
                          `CONSTRAINT_NAME` = CONSTRAINT_NAME_VAR, 
                          `REFERENCED_TABLE_SCHEMA` = REFERENCED_TABLE_SCHEMA_VAR, 
                          `REFERENCED_TABLE_NAME` = REFERENCED_TABLE_NAME_VAR, 
                          `REFERENCED_COLUMN_NAME` = REFERENCED_COLUMN_NAME_VAR, 
                          `INVALID_KEY_COUNT` = @invalid_key_count,
                          `INVALID_KEY_SQL` = CONCAT('SELECT ', 
                              'REFERRING.', '`', COLUMN_NAME_VAR, '` ', 'AS "Invalid: ', COLUMN_NAME_VAR, '", ', 
                              'REFERRING.* ', 
                              @from_part, ';'),
                          `INVALID_KEY_DELETE_SQL` = CONCAT('DELETE ', '`', TABLE_SCHEMA_VAR, '`.`', TABLE_NAME_VAR, '` ',
                              'FROM ', '`', TABLE_SCHEMA_VAR, '`.`', TABLE_NAME_VAR, '`', ' ', 
                              'LEFT JOIN `', REFERENCED_TABLE_SCHEMA_VAR, '`.`', REFERENCED_TABLE_NAME_VAR, '`', ' ', 
                              'ON (', '`', TABLE_SCHEMA_VAR, '`.`', TABLE_NAME_VAR, '`', '.`', COLUMN_NAME_VAR, '`', ' = ', '`', REFERENCED_TABLE_SCHEMA_VAR, '`.`', REFERENCED_TABLE_NAME_VAR, '`', '.`', REFERENCED_COLUMN_NAME_VAR, '`', ') ', 
                              'WHERE ', '`', TABLE_SCHEMA_VAR, '`.`', TABLE_NAME_VAR, '`', '.`', COLUMN_NAME_VAR, '`', ' IS NOT NULL ',
                              'AND ', '`', REFERENCED_TABLE_SCHEMA_VAR, '`.`', REFERENCED_TABLE_NAME_VAR, '`', '.`', REFERENCED_COLUMN_NAME_VAR, '`', ' IS NULL', ';');
                  END IF;
                  DEALLOCATE PREPARE stmt; 
      
              END LOOP foreign_key_cursor_loop;
          END$$
      
      DELIMITER ;
      
      CALL ANALYZE_INVALID_FOREIGN_KEYS('%', '%', 'Y');
      DROP PROCEDURE IF EXISTS ANALYZE_INVALID_FOREIGN_KEYS;
      
      SELECT * FROM INVALID_FOREIGN_KEYS;
      

      【讨论】:

      • 我知道它现在已经过时了,但遇到了这个问题,我想知道:你有什么理由不使用 CONCAT('DELETE REFERRING ', @from_part, ';') 来表示 DELETE 语句?
      猜你喜欢
      • 2011-03-17
      • 2021-07-10
      • 1970-01-01
      • 2011-09-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-10
      • 1970-01-01
      相关资源
      最近更新 更多