MySQL 是否总是 运行 外键约束检查何时更新行?或者仅当相关列发生更改时?

Does MySQL always run foreign key constraint checks when a row is updated? Or only when a relevant column is changed?

我有这个问题,但我没有找到具体的文档来确认该行为,也不知道如何自己手动检查。

考虑我有 table A 和 b_id 到 table B 的外键。如果我 运行 对 table A 中的一行进行更新, mysql 是否总是 运行 外键约束检查 table B 即使 A 的 b_id 保持不变或未在更新语句中传递?比如(select 1 from B where id = ?)

示例:

UPDATE A set A.name = "x", A.b_id = 1 where A.id = 1 我知道这个 运行B

上的外键检查

UPDATE A set A.name = "x" where A.id = 1 但是即使 b_id 由于未通过而保持不变,这是否也 运行 外键检查?

UPDATE A set A.name = "x" A.b_id = A.b_id where A.id = 1 那这个呢? b_id 以相同的现有值传入。 fk 是否检查 运行?

我们将不胜感激任何支持文档或帮助,以及关于如何自己测试此类行为的提示,因为使用 EXPLAIN 没有帮助。

编辑:这是针对 INNODB 引擎和 mysql 8.0

当服务器检测到数据被实际更改并需要保存到磁盘时,总是检查外键完整性。它在所有 BEFORE UPDATE 触发器执行之后进行检查(而不是每次在每个单独的触发器之前和之后执行的数据类型检查)。

原因很简单。服务器不存储任何标记值是否已更改的标志 - 它比在实际、物理、UPDATE 执行之前直接比较更昂贵。不跟踪值更改。服务器不知道更新期间要保存的值是由查询文本提供的,还是由触发器链中的 BEFORE UPDATE 触发器之一提供的。

small DEMO

添加外键约束时,MySQL requires an index:

MySQL requires that foreign key columns be indexed; if you create a table with a foreign key constraint but no index on a given column, an index is created.

澄清一下,这是引用 table(在您的示例 A 中)中的索引,而不是引用的 table(在您的示例 B 中)。 table B 中的索引你必须自己提供,否则你会得到一个错误信息。

鉴于此索引,您问题的答案可以概括为:

每当修改此索引时,将验证外键约束(例如,检查引用的行是否存在于 table 中)。

由于发生这种情况时可能并不完全明显,举一些例子:table of

CREATE TABLE A (id int primary key, b_id int, c int, 
    FOREIGN KEY (b_id) REFERENCES B(id))

检查外键(例如,如果该值存在于引用的 table B 中)(假设存在这样的行):

update A set id = 2 where id = 1 

因为它修改了主键,它是每个索引的一部分,所以隐藏索引被修改,因此将执行验证,并且

update A set b_id = 3 where b_id = 2

因为它修改了列 b_id,它是隐藏索引的一部分,所以隐藏索引也被修改了。

未检查

update A set id = 2 where id = 2 
update A set b_id = 3 where b_id = 3
update A set b_id = b_id

因为那些不修改索引内容(因为没有任何变化)。

值得注意

update A set c = 2 where c = 3

也不修改索引(因为 c 不是索引的一部分),因此即使行本身发生更改,也不会验证外键约束。

为了使事情更复杂一点,您可以使用自己的索引:

CREATE TABLE A (id int primary key, b_id int, c int, 
    INDEX b_c (b_id, c),
    FOREIGN KEY (b_id) REFERENCES B(id))

如果您这样做,MySQL 可以将此索引用作您的外键,而不必添加自己的隐藏索引。上述更新的效果不会改变,除了

update A set c = 2 where c = 3

现在,c 是 MySQL 用于外键约束的索引列,并且此更新需要修改索引内容 - 因此会触发验证。这实际上是一个逻辑上不需要的验证 - 它只是因为 MySQL 实现它的方式而发生。所以如果你真的想防止所有不必要的检查,你可以自己在列 b_id 上添加一个索引,然后 MySQL 就不会使用 (b_id, c) 上的索引。但那将是极端的 micro-optimization,你真的不应该仅仅为了这个而这样做。

请注意,虽然这可能是一个值得了解的有趣技术细节,但它不会对您实际使用数据库执行的操作产生任何影响。您不应该根据这些知识以不同的方式编写您的查询。如果需要更新一行,则必须更新该行。此外,添加索引只是为了防止检查肯定不会有帮助。此外,由于未指定行为,它也可以随时更改(例如,如果您找到并实施更好的解决方案,则由您更改)。