多字段Unique index实现方案:高并发OLTP大表如何避免重复数据
高并发OLTP 5000万行大表去重可行方案
你提到的三个现有方案中,方案3可通过数据库原生能力解决表结构变更维护繁琐的问题,除此之外还有多个适配不同场景的可选方案:
方案1:数据库自动生成哈希列唯一约束
针对你担心的应用侧维护哈希字段繁琐的问题,可直接用数据库自带的**生成列(Generated Column)**能力实现哈希值自动计算、自动更新,无需应用侧介入:
- 仅需新增1个存储哈希值的生成列,无需修改业务写入逻辑,数据库自动根据配置的规则计算全字段哈希值
- 语法示例(MySQL):
-- 新增自动生成的哈希列,CONCAT_WS用不会出现在业务字段的特殊字符做分隔符避免拼接歧义 ALTER TABLE your_table ADD COLUMN content_hash CHAR(32) GENERATED ALWAYS AS (MD5(CONCAT_WS('^_^', col1, col2, ..., col30))) STORED; -- 对哈希列创建唯一索引,索引大小仅为原30字段联合索引的1/10不到 CREATE UNIQUE INDEX uk_content_hash ON your_table(content_hash);
- 表结构变更需要调整去重字段时,仅需修改生成列的计算规则,全表更新可通过分批DML执行避免锁表,对业务无侵入
- 若担心单哈希碰撞,可新增2个核心高区分度字段和哈希列做联合唯一索引,碰撞概率可忽略不计
方案2:核心字段前置校验+数据库轻量兜底
如果业务上可筛选出2-3个区分度极高的核心字段(比如用户ID+业务流水号+操作类型),可采用分层校验的方案最大化降低性能损耗:
- 先对2-3个核心字段创建普通联合索引,写入前应用侧先查询核心字段匹配的行,对比剩余字段是否完全一致,一致则拒绝写入
- 数据库层可加轻量约束兜底,MySQL用
INSERT ... ON DUPLICATE KEY UPDATE、PostgreSQL用INSERT ... ON CONFLICT DO NOTHING避免重复写入报错 - 该方案索引开销仅为全字段唯一索引的5%不到,读写性能几乎不受影响,适合对延迟要求极高的核心OLTP场景
方案3:数据库原生行级校验约束
如果不想改动表结构,可使用部分数据库支持的原生行内容校验能力,无需额外存储字段即可实现去重:
- PostgreSQL可直接基于行内容的哈希值创建排除约束,语法示例:
ALTER TABLE your_table ADD CONSTRAINT no_duplicate_row EXCLUDE USING btree (md5(your_table::text) WITH =);
- 该方案无需新增字段,表结构增删字段时自动适配哈希计算规则,无需手动修改配置
- 仅适合支持行序列化和排除约束的数据库,性能略低于生成列哈希索引方案
方案4:写入链路前置缓存去重
如果重复写入请求大多是短时间内的重复提交场景,可在写入链路增加旁路缓存做第一层校验:
- 用Redis缓存最近1-2小时写入的行哈希值,写入前先查缓存是否存在对应哈希,存在则直接拒绝写入,不存在则执行数据库写入操作,同时把哈希写入Redis设置过期时间
- 数据库层仅做冷数据的重复校验,日常写入性能损耗可降到1%以下
- 适合重复请求集中在热点数据的场景,无需修改数据库表结构
选型参考
- 无特殊性能要求优先选数据库自动生成哈希列+唯一索引方案,兼顾可维护性、性能、可靠性
- 对写入延迟要求极高的场景选核心字段前置校验+数据库兜底方案
- 不想改动表结构且数据库支持对应能力选原生行级校验约束方案
- 重复请求集中在短时间热点场景选前置缓存去重方案
内容的提问来源于stack exchange,提问作者Alez
相关产品推荐
相关产品推荐

