MySQL (Percona) 错误 1452:无法无故添加或更新子行

MySQL (Percona) Error 1452: Cannot add or update a child row for no reason

我有 2 个数据库表“用户”和“任务”。 任务 table 包含 createdBy_user_idupdatedBy_user_id 列的两个外键,它们都引用 users.id。如果我尝试向 tasks 插入一个条目(在确保外键引用的用户存在之后),如下所示:

INSERT INTO tasks (createdBy_user_id,updatedBy_user_id,noOfYear,createdAt,updatedAt,status,customer_id)
VALUES (1,1,1,NOW(),NOW(),"open",1)

查询失败,错误 1452:

ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`test`.`tasks`, CONSTRAINT `user_id_fk_constr` FOREIGN KEY (`createdBy_user_id`) REFERENCES `users` (`id`))

我不知道为什么会这样,因为如果我删除约束,一切都会正常。 “updatedBy_user_id”列不会发生同样的错误,这让这让人感到困惑。

这些表具有以下 DDL:

用户table:

CREATE TABLE `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `active` tinyint(1) DEFAULT NULL,
  `email` varchar(255) DEFAULT NULL,
  `email_confirmed_at` datetime DEFAULT NULL,
  `username` varchar(50) NOT NULL,
  `password` varchar(255) NOT NULL,
  `first_name` varchar(50) DEFAULT NULL,
  `last_name` varchar(50) DEFAULT NULL,
  `job` varchar(64) DEFAULT NULL,
  `position` varchar(64) DEFAULT NULL,
  `specialKnowledge` text,
  `tasks` text,
  PRIMARY KEY (`id`),
  UNIQUE KEY `username` (`username`),
  UNIQUE KEY `email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

任务table:

CREATE TABLE `tasks` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `createdAt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updatedAt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `noOfYear` int(11) NOT NULL,
  `year` int(11) GENERATED ALWAYS AS (right(year(`createdAt`),2)) VIRTUAL NOT NULL,
  `createdBy_user_id` int(11) NOT NULL,
  `updatedBy_user_id` int(11) NOT NULL,
  `status` enum('open','closed') NOT NULL,
  `customer_id` int(11) NOT NULL,
  `projectDescription` text,
  PRIMARY KEY (`id`),
  UNIQUE KEY `tasks_year_unique_constr` (`year`,`noOfYear`),
  KEY `user_id_fk_constr` (`createdBy_user_id`),
  KEY `customer_id_fk_constr` (`customer_id`),
  KEY `user_up_id_fk_constr` (`updatedBy_user_id`),
  CONSTRAINT `customer_id_fk_constr` FOREIGN KEY (`customer_id`) REFERENCES `Customer` (`id`),
  CONSTRAINT `user_id_fk_constr` FOREIGN KEY (`createdBy_user_id`) REFERENCES `users` (`id`),
  CONSTRAINT `user_up_id_fk_constr` FOREIGN KEY (`updatedBy_user_id`) REFERENCES `users` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 

如您所见,数据类型匹配并且 table 都使用 InnoDB Engine。 用户 table 包含一项:

select id,username from users;
+----+----------+
| id | username |
+----+----------+
|  1 | admin    |
+----+----------+
1 row in set (0.00 sec)

所以没有明显的原因,为什么插入会失败。你知道我的 table 有什么问题吗?看起来像是软件错误。

这是一个错误。 MySQL 5.7 在生成列和外键方面遇到了一些问题,我认为这是 Bug #79772 Foreign key not allowed when a virtual index exists.

的未修复变体

在您的特定情况下,可能取决于您的确切版本,以下任何修改似乎都可以防止该错误的发生:

  • 不使用虚拟列,但使其成为stored
  • 不要为直接跟在虚拟列之后的列创建外键,例如将列顺序更改为 year, status, createdBy_user_id, updatedBy_user_id.
  • 不要在虚拟列上使用 unique 索引,普通索引应该没问题(至少在修复了链接错误的版本中)。您需要一个唯一的约束,所以这不是一个选项,但是这可以解决您的问题这一事实强调了问题的“错误”性质。

第二个要点似乎是潜在的错误:我假设某些迭代器没有正确计算虚拟列,因此应该检查 createdBy_user_id 的外键似乎混淆了列,实际上针对用户 table 检查 year 的值(在本例中为“20”)。因此,如果您的用户 table 中有一个 ID 为“20”的用户,外键实际上会接受这个,无论您尝试插入的 createdBy_user_id 的值是什么,请参阅 MySQL 5.7.29 fiddle.

除非您有特定理由使用虚拟列,否则使用 stored 可能是理智的做法。