Skip to content

在数据库中使用“一锁二查三更新”策略仍出现重复数据,通常由以下原因导致:


1. 锁的粒度不足

  • 问题:如果锁定的是行级锁范围不正确,其他事务仍可能插入重复数据。
    例如:两个事务同时检查某唯一字段(如用户名)是否存在,均未找到记录,导致都成功插入。此时行锁无法阻止插入,因为查询时未命中任何行,导致未加锁。
  • 解决方案
    使用间隙锁(Gap Lock)表级锁 锁定可能插入的范围。例如:
    sql
    SELECT * FROM table WHERE unique_field = 'value' FOR UPDATE; -- 行锁
    -- 或者使用间隙锁(如MySQL的Next-Key Lock)

2. 未使用唯一约束

  • 问题:仅依赖应用层逻辑检查唯一性,但数据库层面缺少唯一索引。高并发时,多个事务可能同时通过检查并插入重复数据。
  • 解决方案
    强制数据库唯一性
    sql
    ALTER TABLE table ADD UNIQUE INDEX idx_unique_field (unique_field);
    数据库唯一索引是防止重复的最后防线,即使应用层逻辑有漏洞,也能通过唯一约束报错拦截。

3. 事务隔离级别不当

  • 问题:在可重复读(Repeatable Read) 隔离级别下,若未正确使用锁,可能出现幻读(Phantom Read)。例如,事务A查询未发现数据,事务B插入并提交后,事务A仍能插入相同数据。
  • 解决方案
    • 使用 SELECT ... FOR UPDATE 显式锁定查询范围(加间隙锁)。
    • 将隔离级别提升为 Serializable(可能牺牲性能)。

4. 查询与更新之间存在时间差

  • 问题:即使锁定数据,若查询后锁被释放(如非事务内操作),其他事务仍可能插入重复数据。
  • 解决方案
    确保整个操作在事务内完成
    sql
    BEGIN;
    SELECT * FROM table WHERE unique_field = 'value' FOR UPDATE; -- 加锁
    -- 检查结果,若无数据则插入
    INSERT INTO table (unique_field) VALUES ('value');
    COMMIT;

5. 分布式环境下的锁失效

  • 问题:在分布式系统中,若未使用分布式锁,不同节点的线程可能同时通过检查并插入数据。
  • 解决方案
    • 使用 Redis分布式锁ZooKeeper 协调全局唯一性。
    • 最终仍需依赖数据库唯一约束兜底。

总结

  • 核心原则:应用层检查 + 数据库唯一约束 + 合理锁机制,三者缺一不可。
  • 推荐方案
    1. 为关键字段添加唯一索引。
    2. 在事务中使用 SELECT ... FOR UPDATE 锁定查询范围。
    3. 处理唯一键冲突异常(如MySQL的 Duplicate entry 错误),在应用层友好提示用户。

通过上述组合策略,可彻底避免重复数据问题。

文章来源于自己总结和网络转载,内容如有任何问题,请大佬斧正!联系我