MySQL的基本原理"无法在FROM子句中指定更新的目标表"

Luk*_*der 5 mysql sql sql-update sql-delete

在MySQL中,如果我正在执行一个DELETEUPDATE在同一个表上,我不能在谓词中重用表.例如,这是不可能的:

DELETE FROM story_category WHERE category_id NOT IN (
--          ^^^^^^^^^^^^^^ deleting from this table...

SELECT DISTINCT category.id FROM category 
       INNER JOIN story_category ON category_id=category.id);
--                ^^^^^^^^^^^^^^ ... prevents using the same table in predicates
Run Code Online (Sandbox Code Playgroud)

上面的例子来自这个Stack Overflow问题,其中接受的答案和许多其他答案指出,通过将子查询嵌套到另一个子查询中以使MySQL无法识别表重用,可以使用一种技巧:

DELETE FROM story_category
--          ^^^^^^^^^^^^^^
WHERE category_id NOT IN (
    SELECT cid FROM (
        SELECT DISTINCT category.id AS cid FROM category 
        INNER JOIN story_category ON category_id=category.id
--                 ^^^^^^^^^^^^^^ apparently no longer considered the same table
    ) AS c
)
Run Code Online (Sandbox Code Playgroud)

现在,这种解决方法以及甚至可能引发两个大问题的事实:

  • 错误检查显然没有很好地实现.一个简单的查询转换,MySQL不再认识到它试图禁止(希望)一个充分理由的情况.
  • 因为可能有一个很好的理由,所以解决这个限制可能不是一个好主意.

我怀疑在谓词中阻止这种访问的原因与MySQL试图建立的关于ACID-ness的一些保证有关.我担心如果解决这个限制,我可能会破坏我的数据或在边缘情况下产生有趣的竞争条件.

遗憾的是,MySQL手册对于此错误的基本原理并不十分明确:

目前,您无法更新表并从子查询中的同一表中进行选择.

所以,我的问题是:

是什么原因导致MySQL不允许这种非常常见的习惯用法,其中来自UPDATEDELETE语句的表不能在任何谓词中引用?

nun*_*nks 2

文档中建议了该解决方法,其中解释说它之所以有效,是因为数据被具体化到临时表中,作为优化器“技巧”以获得更好的性能。由于它已被记录,我认为使用它没有任何警告。

文档中解决方法的“最小”示例是:

UPDATE t ... WHERE col = (SELECT * FROM (SELECT ... FROM t...) AS dt ...);
Run Code Online (Sandbox Code Playgroud)

我也找不到解释为什么来自子查询的简单更新在 MySQL 中不起作用,但这种具体化解决方法让我认为它与解决锁定冲突有关:您无法获得行更新的独占锁因为它已经有一个 SELECT 的共享锁。当 SELECT 来自第二个“具体化”临时表时,不存在冲突。

也就是说,我承认这没有多大意义,因为 InnoDB 文档详细解释了它支持元组多版本和所有四个锁隔离级别,因此应该像 PostgreSQL、Oracle 和其他语言一样支持这种操作。

也许它给出的错误消息是 MyISAM 实现的剩余故障保护,因为它只支持全表锁,但我不知道为什么它应该适用于其他引擎。