You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

从锁定行取值插入时的事务阻塞问题原因及解决方法咨询

问题原因分析

嘿,这个现象其实是InnoDB锁机制和事务读取模式共同作用的结果,我给你拆解一下:

首先看第一个事务里的update product set price = 70;——这个语句没有指定WHERE条件,相当于要更新整个product表的所有行。InnoDB引擎会给这个表加上意向排他锁(IX锁),同时给表中每一行都加上行级排他锁(X锁)。虽然你之后执行了rollback,但在sleep的20秒里,这些锁都是一直持有的。

然后重点来了:第二个事务里的insert into product_order select ... from product,这个语句里的select并不是普通的快照读(也就是我们平时说的不加锁读,用undo log返回历史版本),而是当前读——InnoDB对DML语句中的查询部分,都会强制使用当前读,目的是获取最新的数据,并且会尝试给读取的行加上共享锁(S锁)。

而排他锁(X)和共享锁(S)是互斥的:第一个事务已经把所有行都加了X锁,第二个事务要加S锁就必须等X锁释放,所以它只能一直等到第一个事务sleep结束、rollback释放锁之后才能执行。

你觉得“没更新第二个事务涉及的行”,但实际上第一个事务是全表更新,所有行都被锁了,第二个事务要读所有行,自然就产生锁冲突了。

解决办法

根据你的需求,有几个可行的方案:

  • 精准更新,避免全表锁:如果你的业务逻辑里第一个事务不需要更新全表,一定要给update加上WHERE条件,并且确保WHERE条件能用到索引——这样InnoDB只会给匹配的行加锁,不会影响其他行的读取。比如:

    update product set price =70 where id=1;
    
  • 拆分第二个事务的读写操作:把insert...select拆成两步,先做普通的快照读获取数据,再执行insert。普通select在默认的可重复读(RR)隔离级别下是快照读,不会请求锁,也就不会被阻塞:

    -- 第二个事务改成这样
    START TRANSACTION;
    -- 第一步:快照读,获取product的数据(不会等锁)
    CREATE TEMPORARY TABLE tmp_product AS SELECT id, amount, price FROM product;
    -- 第二步:从临时表插入数据
    INSERT INTO product_order(product_id, amount, price) SELECT id, amount, price FROM tmp_product;
    DROP TEMPORARY TABLE tmp_product;
    COMMIT;
    
  • 调整事务隔离级别(谨慎使用):把第二个事务的隔离级别改成读已提交(RC),不过这个办法要注意业务影响。RC隔离级别下,虽然insert...select还是当前读,但InnoDB的锁范围会更小,而且不会有间隙锁的问题,但它可能导致不可重复读的问题,需要结合你的业务场景评估。设置方式:

    -- 在第二个事务开始前设置
    SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
    START TRANSACTION;
    insert into product_order(product_id, amount, price) select id, amount, price from product;
    commit;
    

内容的提问来源于stack exchange,提问作者Artsiom Kotau

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 08:34:03