OLD和NEW是只读伪记录,不能作为表出现在FROM子句中;只能用OLD.column_name或NEW.column_name访问字段,且仅限触发器内部使用。

OLD 和 NEW 不是表,不能出现在 FROM 子句里
直接写 SELECT * FROM OLD 或 SELECT NEW.id 会报错 ERROR 1146: Table 'xxx.OLD' doesn't exist —— 因为 OLD 和 NEW 是只读的“伪记录”(transition variables),不是真实表或临时表,不支持 FROM、JOIN、WHERE 中作为数据源引用。
它们只能用点号访问字段,且必须在触发器体内部使用
OLD 和 NEW 的唯一合法用法是 OLD.column_name 或 NEW.column_name,且仅限于触发器 BEGIN...END 块内。常见误用包括:
- 试图在子查询中引用:
SELECT id FROM (SELECT OLD.id) t→ 语法错误 - 在 INSERT INTO ... SELECT 中把
OLD当作表名:INSERT INTO log SELECT * FROM OLD→ 报错 - 用
CONCAT('id:', OLD.id)没问题,但SELECT CONCAT('id:', OLD.id) FROM DUAL多余且易混淆上下文
想“查 OLD/NEW”?实际要的是字段值,不是查表
你真正需要的往往只是字段内容,而非模拟查询行为。正确做法是直接取值并用于其他语句:
- 归档整行:用
INSERT INTO history_table SELECT OLD.*—— 注意这是特殊语法,OLD.*只在SELECT的SELECT列表中被允许,不能出现在FROM - 条件判断:
IF OLD.status != NEW.status THEN ... - 插入日志:
INSERT INTO audit_log (user_id, old_role, new_role) VALUES (OLD.id, OLD.role, NEW.role)
跨库或复杂逻辑时容易踩坑
如果想把 OLD 数据写入另一库的表,必须用完整限定名,且不能依赖“查出来再插”:
- ✅ 正确:
INSERT INTO other_db.audit_log SELECT OLD.id, OLD.name, NOW() - ❌ 错误:
INSERT INTO other_db.audit_log SELECT * FROM OLD(FROM OLD无效) - ⚠️ 注意:
OLD字段类型严格匹配原表,若目标表字段太窄(如VARCHAR(10)接收OLD.long_text),会静默截断,看起来像 NULL
最常被忽略的一点:哪怕只是写 SELECT OLD.id 单独一行,只要不在赋值或函数调用上下文中,MySQL 就会报错——它不接受孤立的字段引用,必须嵌入到有效语句结构里。


















