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

SET FOREIGN_KEY_CHECKS=0;

之前,然后:

SET FOREIGN_KEY_CHECKS=1;

之后

加载完成后,我想判断更新表中的数据是否仍然保持引用完整性——新行没有打破外键约束——但似乎没有办法做到这一点.

作为测试,我输入了我确定违反外键约束的数据,在重新启用外键判断后,mysql没有产生任何警告或错误.

如果我试图找到一种方法来指定表的加载顺序,并在加载过程中保持外键判断处于打开状态,这将不允许我在具有自引用外键约束的表中加载数据,因此这不是一个可接受的解决方案.

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

推荐答案

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;

可以使用此存储过程判断all数据库中是否存在无效的外键.

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

    • 如果是'Y'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_KEY_SQL列或INVALID_FOREIGN_KEYS列的无效引用行.

如果引用列(又名foreign index)和引用列(通常是主键)上都有索引,那么这个存储过程将非常快.

Mysql相关问答推荐

为什么双破折号(--)会导致此MariaDB条款的计算结果为True?

无法删除或更新父行:外键约束无法删除表

MySQL RDS ALTER TABLE ENUM短暂中断了我的数据库连接

在时间戳上搜索大的MySQL表很慢

从表中动态删除所有空列

没有 ORDER BY 的查询性能很高,有 ORDER BY 的查询速度慢得像爬行一样

mysql 代码给我的外键格式不正确

根据名称将值从一个字段复制到不同记录的同一字段

如何更新一个巨大的表的 50k 行?

如何进行查询以在两个不同的列中搜索两个不同的数据字符串?

在 MYSQL 8 中的 JSON 字段上的 SELECT 中返回所有键及其数组项

在 MySQL 中使用非空条件 LAG() 函数

为什么 fetch 方法不能获取任何东西?(feat node.js,restAPI)

有没有更好的方法在无限滚动的网页上呈现获取的提要数据?

MYSQL 或 Laravel Eloquent 如何从这个订单中统计细节

MySQLi count(*) 总是返回 1

获取Count(*)占GROUP BY中所有项目数的百分比

MySQL - 使一对值唯一

如何将 mysqldump 的输出拆分为较小的文件?

MySQL获取两个值之间的随机值