触发器锁表的主因是内部SQL语句未走索引导致全表扫描,在高隔离级别下触发间隙锁;BEFORE/AFTER触发时机影响锁持有逻辑,嵌套调用存储过程或UDF会放大锁范围;锁持续时间取决于主事务生命周期,而非触发器执行时间。
很多人一遇到触发器锁表,第一反应就是“这把触发器写的太烂了”。说实话,这话只说对了一半——触发器本身确实不会主动锁整张表,真正让整张表“冻住”的,是它内部执行的SQL语句,尤其是那些没走索引、扫描范围过大或者在高隔离级别下触发了间隙锁的操作。
这是最典型的情况了。比如触发器中写了这样一条语句:UPDATE inventory SET stock = stock - 1 WHERE sku = @sku,但问题在于,sku这个字段根本没建索引。InnoDB这时候就无奈了——只能全表扫描找数据。在REPEATABLE READ隔离级别下,系统会为所有扫描到的行再加上间隙,统统加上Next-Key Lock,结果就跟锁表没什么区别。
长期稳定更新的攒劲资源: >>>点此立即查看<<<
EXPLAIN查一下触发器内这条语句,看type是不是ALL,如果是,说明全表扫描了SHOW CREATE TABLE inventory中sku是否在某索引的最左列WHERE UPPER(sku) = UPPER(@sku),会让索引直接失效utf8mb4_0900_as_cs,参数却是utf8mb4_general_ci,索引照样跳过不认这里有个容易被忽略的细节:BEFORE触发器运行时,主语句还没落盘,但已经持有了目标行的X锁;而AFTER触发器一定在变更之后执行,如果它再对其他表做UPDATE,等于在同一事务里又加了一把锁,很容易形成锁等待的环路。
BEFORE里可以安全地修改NEW.column,改的值会写入最终结果AFTER中改NEW是无效的,而且MySQL 5.7+明确禁止更新触发它的表,否则报错Can't update table 't1' in stored function/triggerAFTER INSERT同时往同一张统计表写数据时,可能因为主键争抢触发死锁,这种情况在并发高的场景下尤其典型有些触发器表面看起来干干净净,结果里面藏着CALL update_stock_log @sku, @qty这样的调用,而那个存储过程内部又做了INSERT INTO stock_log_history SELECT * FROM inserted——如果这个SELECT没限定主键或没加WITH (NOLOCK),就会触发对stock_log_history的全表扫描加锁,反过来阻塞住主表。
SELECT,每次调用都会申请共享锁START TRANSACTION、COMMIT或ROLLBACK,用了就会直接报错,或者引发隐式锁的异常这一点很多人都想错了。触发器锁表时间,其实等于主事务的生命周期,而不是触发器跑完就释放。哪怕触发器只耗时200毫秒,只要事务后面还有SLEEP(30)或者迟迟没有COMMIT,那么锁就挂满30秒毫不含糊。
SHOW PROCESSLIST如果看到状态是Updating或Waiting for table metadata lock,大概率是事务还没结束@transaction.atomic,你根本没写BEGIN,锁却已经挂上了COMMIT,下个请求接着用同一个连接,锁就“继承”过来了说到底,真正要定位问题,得去查performance_schema.data_locks(MySQL 8.0+)或INFORMATION_SCHEMA.INNODB_TRX加INNODB_LOCK_WAITS——而不是只盯着触发器的代码本身。它只是事务链路上的一环,锁行为藏在它执行的每一条语句里。
侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述