为什么我的唯一约束会与外键混淆?
Why is my unique constraint getting confused with a foreign key?
我有一个包含 table 的架构,如下所示(简化):
-------- --------------- ------------
| key | | permission | | resource |
|------| |-------------| |----------|
| id | -----< | id | >----- | id |
| name | | key_id | | name |
-------- | resource_id | ------------
| action |
---------------
permission
table定义脚本是这样的:
CREATE TABLE `permission` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`key_id` INT(11) NOT NULL,
`resource_id` INT(11) NOT NULL,
`action` VARCHAR(32) NOT NULL,
PRIMARY_KEY (`id`),
CONSTRAINT `fk_permission_key` FOREIGN KEY (`key_id`) REFERENCES `key` (`id`),
CONSTRAINT `fk_permission_resource` FOREIGN KEY (`resource_id`) REFERENCES `resource` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
一切正常。然后,我决定我需要对 permission
table 进行唯一约束,以便 action/key/resource 组合只能存在一条记录,所以我这样做:
ALTER TABLE `permission` ADD UNIQUE KEY `uq_permission` (`key_id`, `resource_id`, `action`);
这也很好用。我们处于使用迁移来管理架构更改的环境中,因此我想确保有一个“回滚”脚本。但是当我发出这个命令时:
ALTER TABLE `permission` DROP INDEX `uq_permission`;
我收到这个错误:
1553 - Cannot drop index 'uq_permission': needed in a foreign key
constraint
经过一番摸索后,我发现如果删除外键 fk_permission_key
,我就可以删除唯一约束。
为什么我的唯一约束与一个完全独立的外键纠缠在一起?
当您添加 fk_permission_key
约束时,MySQL 会自动创建一个索引(此行为不同于其他数据库引擎,您需要显式创建此类索引):
mysql> SHOW INDEX FROM `permission`;
+------------+------------+------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
+------------+------------+------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| permission | 0 | PRIMARY | 1 | id | A | 0 | NULL | NULL | | BTREE | | | YES | NULL |
| permission | 1 | fk_permission_key | 1 | key_id | A | 0 | NULL | NULL | | BTREE | | | YES | NULL |
| permission | 1 | fk_permission_resource | 1 | resource_id | A | 0 | NULL | NULL | | BTREE | | | YES | NULL |
+------------+------------+------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
3 rows in set (0.00 sec)
当您创建 uq_permission
索引时,MySQL 显然会删除 fk_permission_key
索引,因为它知道它可以使用您自己的索引。
mysql> SHOW INDEX FROM `permission`;
+------------+------------+------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
+------------+------------+------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| permission | 0 | PRIMARY | 1 | id | A | 0 | NULL | NULL | | BTREE | | | YES | NULL |
| permission | 0 | uq_permission | 1 | key_id | A | 0 | NULL | NULL | | BTREE | | | YES | NULL |
| permission | 0 | uq_permission | 2 | resource_id | A | 0 | NULL | NULL | | BTREE | | | YES | NULL |
| permission | 0 | uq_permission | 3 | action | A | 0 | NULL | NULL | | BTREE | | | YES | NULL |
| permission | 1 | fk_permission_resource | 1 | resource_id | A | 0 | NULL | NULL | | BTREE | | | YES | NULL |
+------------+------------+------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
5 rows in set (0.01 sec)
但是,如果您现在尝试删除 uq_permission
MySQL 投诉,因为外键将不再有可用的索引(而且这次它不够智能,无法自动创建索引).
我不知道是否可以配置自动创建索引,但在这种情况下,我能想到的唯一解决方案是自己提供索引:
mysql> ALTER TABLE `permission` ADD INDEX `fk_permission_key` (`key_id`);
Query OK, 0 rows affected (0.02 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> SHOW INDEX FROM `permission`;
+------------+------------+------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
+------------+------------+------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| permission | 0 | PRIMARY | 1 | id | A | 0 | NULL | NULL | | BTREE | | | YES | NULL |
| permission | 0 | uq_permission | 1 | key_id | A | 0 | NULL | NULL | | BTREE | | | YES | NULL |
| permission | 0 | uq_permission | 2 | resource_id | A | 0 | NULL | NULL | | BTREE | | | YES | NULL |
| permission | 0 | uq_permission | 3 | action | A | 0 | NULL | NULL | | BTREE | | | YES | NULL |
| permission | 1 | fk_permission_resource | 1 | resource_id | A | 0 | NULL | NULL | | BTREE | | | YES | NULL |
| permission | 1 | fk_permission_key | 1 | key_id | A | 0 | NULL | NULL | | BTREE | | | YES | NULL |
+------------+------------+------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
6 rows in set (0.01 sec)
mysql> ALTER TABLE `permission` DROP INDEX `uq_permission`;
Query OK, 0 rows affected (0.02 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> SHOW INDEX FROM `permission`;
+------------+------------+------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
+------------+------------+------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| permission | 0 | PRIMARY | 1 | id | A | 0 | NULL | NULL | | BTREE | | | YES | NULL |
| permission | 1 | fk_permission_resource | 1 | resource_id | A | 0 | NULL | NULL | | BTREE | | | YES | NULL |
| permission | 1 | fk_permission_key | 1 | key_id | A | 0 | NULL | NULL | | BTREE | | | YES | NULL |
+------------+------------+------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
3 rows in set (0.00 sec)
自动索引创建是documented:
MySQL requires indexes on foreign keys and referenced keys so that
foreign key checks can be fast and not require a table scan. In the
referencing table, there must be an index where the foreign key
columns are listed as the first columns in the same order. Such an
index is created on the referencing table automatically if it does not
exist. This index might be silently dropped later if you create
another index that can be used to enforce the foreign key
constraint. index_name, if given, is used as described previously.
我有一个包含 table 的架构,如下所示(简化):
-------- --------------- ------------
| key | | permission | | resource |
|------| |-------------| |----------|
| id | -----< | id | >----- | id |
| name | | key_id | | name |
-------- | resource_id | ------------
| action |
---------------
permission
table定义脚本是这样的:
CREATE TABLE `permission` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`key_id` INT(11) NOT NULL,
`resource_id` INT(11) NOT NULL,
`action` VARCHAR(32) NOT NULL,
PRIMARY_KEY (`id`),
CONSTRAINT `fk_permission_key` FOREIGN KEY (`key_id`) REFERENCES `key` (`id`),
CONSTRAINT `fk_permission_resource` FOREIGN KEY (`resource_id`) REFERENCES `resource` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
一切正常。然后,我决定我需要对 permission
table 进行唯一约束,以便 action/key/resource 组合只能存在一条记录,所以我这样做:
ALTER TABLE `permission` ADD UNIQUE KEY `uq_permission` (`key_id`, `resource_id`, `action`);
这也很好用。我们处于使用迁移来管理架构更改的环境中,因此我想确保有一个“回滚”脚本。但是当我发出这个命令时:
ALTER TABLE `permission` DROP INDEX `uq_permission`;
我收到这个错误:
1553 - Cannot drop index 'uq_permission': needed in a foreign key constraint
经过一番摸索后,我发现如果删除外键 fk_permission_key
,我就可以删除唯一约束。
为什么我的唯一约束与一个完全独立的外键纠缠在一起?
当您添加 fk_permission_key
约束时,MySQL 会自动创建一个索引(此行为不同于其他数据库引擎,您需要显式创建此类索引):
mysql> SHOW INDEX FROM `permission`;
+------------+------------+------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
+------------+------------+------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| permission | 0 | PRIMARY | 1 | id | A | 0 | NULL | NULL | | BTREE | | | YES | NULL |
| permission | 1 | fk_permission_key | 1 | key_id | A | 0 | NULL | NULL | | BTREE | | | YES | NULL |
| permission | 1 | fk_permission_resource | 1 | resource_id | A | 0 | NULL | NULL | | BTREE | | | YES | NULL |
+------------+------------+------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
3 rows in set (0.00 sec)
当您创建 uq_permission
索引时,MySQL 显然会删除 fk_permission_key
索引,因为它知道它可以使用您自己的索引。
mysql> SHOW INDEX FROM `permission`;
+------------+------------+------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
+------------+------------+------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| permission | 0 | PRIMARY | 1 | id | A | 0 | NULL | NULL | | BTREE | | | YES | NULL |
| permission | 0 | uq_permission | 1 | key_id | A | 0 | NULL | NULL | | BTREE | | | YES | NULL |
| permission | 0 | uq_permission | 2 | resource_id | A | 0 | NULL | NULL | | BTREE | | | YES | NULL |
| permission | 0 | uq_permission | 3 | action | A | 0 | NULL | NULL | | BTREE | | | YES | NULL |
| permission | 1 | fk_permission_resource | 1 | resource_id | A | 0 | NULL | NULL | | BTREE | | | YES | NULL |
+------------+------------+------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
5 rows in set (0.01 sec)
但是,如果您现在尝试删除 uq_permission
MySQL 投诉,因为外键将不再有可用的索引(而且这次它不够智能,无法自动创建索引).
我不知道是否可以配置自动创建索引,但在这种情况下,我能想到的唯一解决方案是自己提供索引:
mysql> ALTER TABLE `permission` ADD INDEX `fk_permission_key` (`key_id`);
Query OK, 0 rows affected (0.02 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> SHOW INDEX FROM `permission`;
+------------+------------+------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
+------------+------------+------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| permission | 0 | PRIMARY | 1 | id | A | 0 | NULL | NULL | | BTREE | | | YES | NULL |
| permission | 0 | uq_permission | 1 | key_id | A | 0 | NULL | NULL | | BTREE | | | YES | NULL |
| permission | 0 | uq_permission | 2 | resource_id | A | 0 | NULL | NULL | | BTREE | | | YES | NULL |
| permission | 0 | uq_permission | 3 | action | A | 0 | NULL | NULL | | BTREE | | | YES | NULL |
| permission | 1 | fk_permission_resource | 1 | resource_id | A | 0 | NULL | NULL | | BTREE | | | YES | NULL |
| permission | 1 | fk_permission_key | 1 | key_id | A | 0 | NULL | NULL | | BTREE | | | YES | NULL |
+------------+------------+------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
6 rows in set (0.01 sec)
mysql> ALTER TABLE `permission` DROP INDEX `uq_permission`;
Query OK, 0 rows affected (0.02 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> SHOW INDEX FROM `permission`;
+------------+------------+------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
+------------+------------+------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| permission | 0 | PRIMARY | 1 | id | A | 0 | NULL | NULL | | BTREE | | | YES | NULL |
| permission | 1 | fk_permission_resource | 1 | resource_id | A | 0 | NULL | NULL | | BTREE | | | YES | NULL |
| permission | 1 | fk_permission_key | 1 | key_id | A | 0 | NULL | NULL | | BTREE | | | YES | NULL |
+------------+------------+------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
3 rows in set (0.00 sec)
自动索引创建是documented:
MySQL requires indexes on foreign keys and referenced keys so that foreign key checks can be fast and not require a table scan. In the referencing table, there must be an index where the foreign key columns are listed as the first columns in the same order. Such an index is created on the referencing table automatically if it does not exist. This index might be silently dropped later if you create another index that can be used to enforce the foreign key constraint. index_name, if given, is used as described previously.