Luk*_*der 5 mysql sql sql-update sql-delete
在MySQL中,如果我正在执行一个DELETE或UPDATE在同一个表上,我不能在谓词中重用表.例如,这是不可能的:
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试图建立的关于ACID-ness的一些保证有关.我担心如果解决这个限制,我可能会破坏我的数据或在边缘情况下产生有趣的竞争条件.
遗憾的是,MySQL手册对于此错误的基本原理并不十分明确:
目前,您无法更新表并从子查询中的同一表中进行选择.
是什么原因导致MySQL不允许这种非常常见的习惯用法,其中来自UPDATE或DELETE语句的表不能在任何谓词中引用?
文档中建议了该解决方法,其中解释说它之所以有效,是因为数据被具体化到临时表中,作为优化器“技巧”以获得更好的性能。由于它已被记录,我认为使用它没有任何警告。
文档中解决方法的“最小”示例是:
UPDATE t ... WHERE col = (SELECT * FROM (SELECT ... FROM t...) AS dt ...);
Run Code Online (Sandbox Code Playgroud)
我也找不到解释为什么来自子查询的简单更新在 MySQL 中不起作用,但这种具体化解决方法让我认为它与解决锁定冲突有关:您无法获得行更新的独占锁因为它已经有一个 SELECT 的共享锁。当 SELECT 来自第二个“具体化”临时表时,不存在冲突。
也就是说,我承认这没有多大意义,因为 InnoDB 文档详细解释了它支持元组多版本和所有四个锁隔离级别,因此应该像 PostgreSQL、Oracle 和其他语言一样支持这种操作。
也许它给出的错误消息是 MyISAM 实现的剩余故障保护,因为它只支持全表锁,但我不知道为什么它应该适用于其他引擎。