触发器中建临时表卡住其他查询,是因为insert into...select会隐式加读锁并引发mdl等待;应改用create temporary table as select,且临时表须精简、带主键、不操作原表。

为什么触发器里建临时表会卡住其他查询?
不是临时表本身慢,而是 CREATE TEMPORARY TABLE 在某些写法下会隐式访问源表并加读锁——尤其当用 INSERT INTO ... SELECT 从一张大表取数时,InnoDB 在 REPEATABLE READ 隔离级别下可能对全表或间隙加锁,导致后续所有对该表的 SELECT/UPDATE 进入 Waiting for table metadata lock 状态。
元数据锁(MDL)在这里是“守门人”:只要有一个会话在等这张表的结构变更(比如 ALTER),而你的触发器又恰好在执行中持有该表的 MDL 共享锁(哪怕只是读),新来的普通查询就会排队等你释放锁,而不是直接去查数据。
必须用 CREATE TEMPORARY TABLE AS SELECT 替代 INSERT SELECT
这是最关键的实操动作。前者只依赖一致性读视图,不加任何表级锁;后者会在源表上申请读锁,极易引发锁竞争。
-
CREATE TEMPORARY TABLE tmp_log AS SELECT id, status FROM orders WHERE id = NEW.id;✅ 安全 -
CREATE TEMPORARY TABLE tmp_log (id INT, status VARCHAR(20)); INSERT INTO tmp_log SELECT id, status FROM orders WHERE id = NEW.id;❌ 危险,INSERT INTO ... SELECT会锁源表
如果你需要动态字段或条件判断,宁可先 CREATE TEMPORARY TABLE 建空表,再用 INSERT ... VALUES 或 INSERT ... SELECT 从 DUAL 或常量构造数据,也别碰业务表。
临时表结构要精简,且必须带主键或唯一索引
触发器里只存 ID 和待更新字段就够了,多一列都可能拖慢插入速度,增加锁持有时间。更重要的是:没有主键的临时表,在后续被 JOIN 或 UPDATE 时容易走全表扫描,反过来又会拉长它对源表的间接依赖时间。
- 只要存
user_id和new_last_time,就定义PRIMARY KEY (user_id) - 避免
TEXT、JSON、大VARCHAR字段,它们会放大临时表内存开销 - 不需要
CREATE INDEX—— 临时表生命周期短,B+ 树索引构建成本往往得不偿失
触发器里只写临时表,绝不碰原表
这是绕过 MySQL 引擎级限制(ERROR 1442)的底线。哪怕你只想把 status 改成 'processed',也得忍住,老老实实塞进临时表,然后靠外部机制处理。
- 触发器内只允许:
INSERT INTO tmp_table SELECT ...或INSERT INTO tmp_table VALUES (...) - 禁止:
UPDATE users SET last_order_time = ...、DELETE FROM logs WHERE ... - 禁止在触发器里调用存储过程去更新原表——它仍在同一事务上下文中,照样报错
真正更新交给 EVENT、应用层轮询,或 CDC 工具。否则你只是把锁竞争从“触发器内”转移到“触发器刚退出、事务还没提交”的那一小段窗口,问题没解决,只是更难排查。
最容易被忽略的一点:临时表必须在触发器外显式创建(比如初始化脚本中),不能在触发器里执行 CREATE TEMPORARY TABLE —— MySQL 不允许在触发器中动态建表,会直接报语法错误。这个约束比锁还硬。










