【问题标题】:Why is my unique constraint getting confused with a foreign key?为什么我的唯一约束与外键混淆?
【发布时间】:2020-07-28 16:55:14
【问题描述】:

我有一个包含这样的表的架构(简化):

--------        ---------------        ------------
| key  |        | permission  |        | resource |
|------|        |-------------|        |----------|
| id   | -----< | id          | >----- | id       |
| name |        | key_id      |        | name     |
--------        | resource_id |        ------------
                | action      |
                ---------------

permission 表定义脚本是这样的:

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 表进行唯一约束,这样操作/键/资源组合只能存在一条记录,所以我这样做了:

ALTER TABLE `permission` ADD UNIQUE KEY `uq_permission` (`key_id`, `resource_id`, `action`);

这也很好用。我们处于使用迁移来管理架构更改的环境中,所以我想确保有一个“回滚”脚本。但是当我发出这个命令时:

ALTER TABLE `permission` DROP INDEX `uq_permission`;

我收到此错误:

1553 - 无法删除索引“uq_permission”:在外键中需要 约束

经过一番摸索,我发现如果我删除了外键fk_permission_key,我就可以删除唯一约束。

为什么我的唯一约束会与完全独立的外键纠缠在一起?

【问题讨论】:

  • 创建另一个适合 FK 的索引(例如通过单独的列),然后删除您的索引。
  • 这可能是一个错误。我最近报告了一个最终成为错误的行为。我建议,在 sql server 中尝试一下,看看它是否有效

标签: mysql unique-constraint


【解决方案1】:

当您添加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 需要外键和引用键的索引,以便 外键检查可以很快并且不需要表扫描。在里面 引用表,必须有外键所在的索引 列按相同顺序列为第一列。 这样的 如果没有,则在引用表上自动创建索引 存在。如果您创建,此索引可能会在稍后被静默删除 另一个可用于强制执行外键的索引 约束。 index_name,如果给定,如前所述使用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-09-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多