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

MySQL“无法在FROM子句中指定更新的目标表”背后的基本原理,mysql,sql,sql-update,sql-delete,Mysql,Sql,Sql Update,Sql Delete,在MySQL中,如果在同一个表上执行删除或更新,则无法在谓词中重用表。例如,这是不可能的: 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=cat

在MySQL中,如果在同一个表上执行删除或更新,则无法在谓词中重用表。例如,这是不可能的:

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
上面的示例取自,其中接受的答案和许多其他答案指出,您可以使用一种技巧,将子查询嵌套到另一个子查询中,以诱使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
)
现在,这一解决方法以及它甚至可能的事实提出了两个大问题:

错误检查显然没有很好地实现。一个简单的查询转换,MySQL不再意识到它试图禁止的情况,希望有一个很好的理由。 因为可能有一个很好的理由,所以绕过这个限制可能不是一个好主意。 我怀疑在谓词中阻止这种访问的原因与MySQL试图建立的一些与ACID无关的保证有关。我担心如果绕过这个限制,我可能会损坏我的数据,或者在边缘情况下产生有趣的竞争条件

不幸的是,关于这一错误的基本原理,这一点并不十分明确:

当前,无法更新表并在子查询中从同一表中进行选择

所以,我的问题是: MySQL不允许在任何谓词中都不能引用UPDATE或DELETE语句中的表这一非常常见的习惯用法的原因是什么?

解决方法是,解释说它之所以有效,是因为数据被放入了一个临时表中,这是为了获得更好的性能。因为它是有文档记录的,所以我认为在使用它时没有任何警告

文档中解决方法的最简单示例是:

UPDATE t ... WHERE col = (SELECT * FROM (SELECT ... FROM t...) AS dt ...);
我也找不到一个解释来解释为什么一个简单的子查询更新在MySQL中不起作用,但这种物化解决方案让我认为它与排序锁定冲突有关:您不能为行更新获得排他锁,因为它已经为SELECT提供了一个共享锁。当SELECT来自第二个具体化的临时表时,不存在冲突

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


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

解决方法可行,不是因为缺少错误检查,但因为使用该构造将内部查询具体化为一个匿名临时表,在查询完成时删除该表。。。当然,这已经不是同一张桌子了。只要您的查询写得很好,并且考虑到底层实现,使用该解决方案没有什么坏处。感谢您的反馈。这很有趣,尽管我会说MySQL的查询优化程序的一个更聪明的实现会为我做到这一点。我觉得有点可怕,在某些情况下有一个隐含的临时表,而在其他情况下没有。