UNIQUE索引是底线,不是可选项;Redis前置过滤仅适用于写多读少、重复率极低的高并发场景,且必须配合数据库二次校验,否则因Redis弱一致性、主从延迟、过期失效等问题必然导致漏判或误判。

直接说结论:对海量数据做唯一性校验,UNIQUE索引是底线,不是可选项;Redis前置过滤只在特定高并发写入+低重复率场景下有用,且必须配合双检机制,否则大概率漏判或误判。
为什么不能只靠Redis做唯一性校验
Redis本身不保证强一致性,网络分区、主从延迟、客户端崩溃都可能导致缓存与数据库状态不一致。比如用户A写入成功但Redis未同步,用户B紧接着查Redis发现“不存在”,于是也插入——主键冲突就发生了。更隐蔽的是,Redis过期时间一到,缓存消失,后续请求又会绕过校验。
常见错误现象:Duplicate entry 'xxx' for key 'uk_user_email' 依然频繁出现,说明Redis只是挡掉了部分请求,没真正解决问题。
使用场景限制:
- 仅适用于“写多读少、重复率极低(
- 不能用于金融、账号、订单等强一致性要求的业务
- 必须搭配数据库层的
UNIQUE约束兜底,否则就是裸奔
UNIQUE索引必须覆盖真实业务组合
很多人建了UNIQUE(email),却忽略业务实际需要的是(email, tenant_id)或(phone, country_code)。一旦组合唯一性没被索引覆盖,INSERT ... ON DUPLICATE KEY UPDATE或REPLACE INTO就会失效,或者只按单字段去重,造成逻辑错误。
参数差异要注意:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
-
UNIQUE KEY uk_email_tenant (email, tenant_id)和UNIQUE KEY uk_tenant_email (tenant_id, email)效果不同:前者支持WHERE email = ?高效查询,后者不支持 - 复合唯一索引要严格遵循最左前缀原则,缺失首字段的查询无法走索引
- 字符串字段务必指定长度,比如
VARCHAR(255)加唯一索引时,若不显式声明前缀长度(如email(191)),可能因InnoDB页大小限制导致建索引失败
批量插入时的唯一性处理策略
用LOAD DATA INFILE或INSERT ... VALUES (...), (...), (...)批量导入时,唯一性冲突的默认行为是中断整个语句。这不是你想要的。
实操建议:
- 加
IGNORE:冲突行直接跳过,其余继续,但不会返回哪些被忽略 —— 适合“能丢就丢”的日志类场景 - 用
ON DUPLICATE KEY UPDATE:冲突时更新指定字段,比如updated_at = NOW(),注意避免意外覆盖业务关键字段 - 禁用唯一性检查(
SET unique_checks = 0)只应在离线ETL中临时使用,且必须确保数据已提前去重,否则上线后SHOW INDEX会报错,修复成本极高
性能影响:唯一索引会让每次INSERT多一次B+树查找和潜在的页分裂,但相比应用层循环查再插,仍是数量级的性能优势。
Redis前置过滤的正确写法(如果真要用)
不是简单SETNX完事。必须满足三个条件:
- Redis Key必须包含业务上下文,比如
user:email:sha256('alice@example.com'),而不是email:alice@example.com(防超长Key和特殊字符) - 必须设置合理TTL(建议比业务SLA长20%),且在DB写入成功后主动
DEL该Key(而非依赖过期) - DB写入失败时,需回滚Redis操作(如用Lua脚本保证原子性),否则缓存永久污染
最容易被忽略的一点:Redis过滤只能降低冲突概率,不能替代数据库约束。只要业务表结构里没加UNIQUE,哪怕Redis命中率99.99%,剩下0.01%的冲突仍会导致数据异常,而这种异常往往在数月后才暴露。

















