MySQL 报 ERROR 1093,说白了就是引擎层在“同一张表上既读又写”时直接拦截——这不是你语法写错了,而是数据库的自我保护机制。唯一稳妥的绕过方式:把子查询包成一个带别名的派生表,比如 SELECT ... FROM (SELECT ... FROM t) AS tmp,或者改用 UPDATE JOIN 语法。别想着走捷径,没戏。

有一类特殊情况能直接嵌套:子查询返回单行单列(标量子查询)。但一旦涉及多行、多列,或者引用了被更新的表本身,立刻报错。下面展开讲几个关键场景。
MySQL 里写 UPDATE ... SET col = (SELECT ...) 为什么报 ERROR 1093?
这是 MySQL 对“同一张表既查又改”的硬性限制——不是语法错,而是解析阶段就被引擎层拦下。哪怕子查询只是读本表某个字段做判断,只要它在 FROM 里显式出现,就会触发 1093。
- 唯一绕过方式:让子查询不显式出现被更新表名。用
WHERE c.id = orders.customer_id这种外层字段隐式引用,而不是FROM orders o2。 - 如果子查询里确实要查
orders表(比如找每个用户的最新订单时间),必须包装成派生表:SELECT * FROM (SELECT ...) AS tmp。 - 别指望加
LIMIT 1就能糊弄过去——ERROR 1093 在解析阶段就抛出了,根本不会执行到 LIMIT。
PostgreSQL / SQL Server 中 UPDATE ... FROM 和子查询的关系
这两个数据库都支持 UPDATE ... FROM 语法,但子查询仍然不能随意嵌套。一旦子查询含 GROUP BY、窗口函数或者外部引用,照样可能报 “correlated subquery not allowed here”。
- PostgreSQL 允许
UPDATE t SET a = s.x FROM (SELECT ...) s WHERE t.id = s.id,但如果子查询里写WHERE s.ref = t.id(反向引用主表),会失败。 - SQL Server 的
UPDATE t SET ... FROM t JOIN (SELECT ...) s ON ...更灵活,但必须确保子查询结果对主表每行最多匹配一次——否则最后一条会覆盖前面的更新,结果完全不可控。 - 所有数据库都要求子查询结果集在 JOIN 条件下与目标表一对一映射,否则语义模糊,谁也救不了。
SET (c1, c2) = (SELECT a, b ...) 这种多列赋值到底能不能用?
MySQL 8.0.19+ 开始支持这个语法,但生产环境建议慎用——表面简洁,实际脆弱。
- 子查询必须严格返回一行两列,而且
SELECT列表里不能出现外层表字段(比如orders.id),否则直接报错。 - 没有索引支撑时,性能比等价的
JOIN差不少;出错提示也模糊——只报Operand should contain 1 column(s),实际原因可能是行数超限。 - PostgreSQL 原生支持,但 SQL Server 完全不认这种写法,跨库迁移时必然断裂。
- 真正稳的做法仍然是
UPDATE t1 JOIN (SELECT ...) s ON t1.id = s.id SET t1.a = s.a, t1.b = s.b。
最后提一个最容易被忽略的点:子查询是否返回 NULL,直接决定更新结果。如果没有加 WHERE EXISTS 或者 COALESCE 包裹,匹配不到的行就默默被设为 NULL——而不是跳过。这种隐性错误比语法报错更难排查,往往要在数据异常时才反应过来。
