在数据库中使用“一锁二查三更新”策略仍出现重复数据,通常由以下原因导致:
1. 锁的粒度不足
- 问题:如果锁定的是行级锁或范围不正确,其他事务仍可能插入重复数据。
例如:两个事务同时检查某唯一字段(如用户名)是否存在,均未找到记录,导致都成功插入。此时行锁无法阻止插入,因为查询时未命中任何行,导致未加锁。 - 解决方案:
使用间隙锁(Gap Lock) 或 表级锁 锁定可能插入的范围。例如:sqlSELECT * 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. 查询与更新之间存在时间差
- 问题:即使锁定数据,若查询后锁被释放(如非事务内操作),其他事务仍可能插入重复数据。
- 解决方案:
确保整个操作在事务内完成:sqlBEGIN; SELECT * FROM table WHERE unique_field = 'value' FOR UPDATE; -- 加锁 -- 检查结果,若无数据则插入 INSERT INTO table (unique_field) VALUES ('value'); COMMIT;
5. 分布式环境下的锁失效
- 问题:在分布式系统中,若未使用分布式锁,不同节点的线程可能同时通过检查并插入数据。
- 解决方案:
- 使用 Redis分布式锁 或 ZooKeeper 协调全局唯一性。
- 最终仍需依赖数据库唯一约束兜底。
总结
- 核心原则:应用层检查 + 数据库唯一约束 + 合理锁机制,三者缺一不可。
- 推荐方案:
- 为关键字段添加唯一索引。
- 在事务中使用 SELECT ... FOR UPDATE 锁定查询范围。
- 处理唯一键冲突异常(如MySQL的
Duplicate entry
错误),在应用层友好提示用户。
通过上述组合策略,可彻底避免重复数据问题。