MySQL中IF语句引发的竞争条件及锁策略问询
跨表唯一性竞争问题的解决方案
核心结论:无需强制加表锁,可通过原子操作或行级锁实现互斥
针对你遇到的并发竞争问题,以下是几种无需依赖表锁的可行方案:
方案1:引入全局唯一关联表
创建一张仅用于维护全局唯一性的表global_unique_x,将x_value设为唯一主键:
CREATE TABLE global_unique_x ( x_value INT PRIMARY KEY );
所有涉及插入x到A或B表的操作,都必须先尝试插入这张表:
- 若插入
global_unique_x(x)成功,说明x未被任何表占用,可安全执行后续逻辑(检查A表无x后插入B表) - 若插入失败,直接终止操作,避免重复或冲突
这种方式利用单表唯一约束的原子性实现全局互斥,InnoDB会自动处理行锁,无需手动加表锁。
方案2:原子化INSERT ... SELECT操作
将判断与插入逻辑合并为一个原子SQL,放在事务中执行:
START TRANSACTION; -- 仅当A表无x且B表无x时,插入B表 INSERT INTO B (COLUMN_B) SELECT x FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM A WHERE COLUMN_A = x) AND NOT EXISTS (SELECT 1 FROM B WHERE COLUMN_B = x); COMMIT;
InnoDB执行该语句时会对A表相关范围加间隙锁(默认REPEATABLE READ隔离级别下),阻止其他事务插入x到A表,同时原子性完成判断与插入,消除中间竞争窗口。
方案3:使用SELECT ... FOR UPDATE加行级锁
在事务中先锁定A表的潜在行,再执行业务逻辑:
START TRANSACTION; -- 锁定A表中COLUMN_A=x的行,不存在则锁定对应间隙 SELECT 1 FROM A WHERE COLUMN_A = x FOR UPDATE; -- 此时其他事务无法插入x到A表 IF NOT EXISTS (SELECT 1 FROM A WHERE COLUMN_A = x) THEN INSERT INTO B (COLUMN_B) SELECT x FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM B WHERE COLUMN_B = x); END IF; COMMIT;
SELECT ... FOR UPDATE会持有锁直到事务提交,确保判断与插入期间无其他事务能修改A表的x相关数据,避免竞争。
为什么不推荐表锁?
InnoDB的行级锁和间隙锁已能实现细粒度互斥,表锁会大幅降低并发性能,上述方案均通过原子操作或行级锁避免了全局表锁的需求。
内容的提问来源于stack exchange,提问作者helloSQL
相关产品推荐
相关产品推荐

