对于引用约束,在mysql实现中引用表和被引用表都会记录表的引用关系。

以下链表记录该表引用了哪些表,链表中每个元素为 dict_foreign_t

以下链表记录该表被哪些表引用,链表中每个元素为 dict_foreign_t

dict_foreign_t 结构如下

  1. dict_foreign_t
  2. {
  3. foreign_index
  4. referenced_index // idx1
  5. ......
  6. }

对于上面的测例,t1为被引用表,t2为引用表。

对于删除索引操作,如果索引涉及到引用关系,那么对应引用关系中的索引也应该做相应调整。对于测例中的删索引操作 alter table t1 drop key idx1; 应该做如下调整

  • 修改t1表的引用关系 table->referenced_list中dict_foreign_t->referenced_index 置为null

  • 修改t2表的引用关系 table->foreign_list中dict_foreign_t->referenced_index 置为null

而此bug修复之前, 并没有修改t2表的引用关系, 从而导致后面对t2表进行DML操作时,如果访问了无效的dict_foreign_t->referenced_index就会导致crash。

如果删除引用表对应的外键时,mysql如何处理的呢?同删除被引用表的索引一样,都需要调整引用表和被引用表的关系。

实际上,当删除引用表对应的外键时,如果存在和此外键相似(这里的相似是指索引列数和列顺序相同)的索引时,会用相似的索引代替删除的外键,从而保持原有的引用约束关系。

例如,在下面的例子中,原来t2存在有两个索引idx1和idx2,都可以作为外键,函数选择了idx1作为外键。当删除idx1后,同样通过dict_foreign_find_index选择了idx2做为了外键。

同样,此bug修复后,删除被引用表的索引时,如果存在相似的索引会用相似的索引代替。 例如,在下面的例子中,原来t1存在有两个索引idx1和idx2,都可以作为被引用索引,函数dict_foreign_find_index选择了idx1作为被引用索引。当删除idx1后,同样通过dict_foreign_find_index选择了idx2做为了新的被引用索引。

  1. drop table if exists t2;
  2. drop table if exists t1;
  3. create table t1 (a int, b int, key idx1(a), key
  4. idx2(a)) engine=innodb;
  5. foreign key (b) references t1(a)) engine=innodb;

其实,相似索引的存在是完全没有必要的。如果禁止创建相似的索引,那么引用约束这块的处理也不会这么复杂了。