Spring Boot+MySQL:不依赖数据库唯一约束实现Topic唯一校验
解决方案
核心思路
确保仅活跃状态的Topic的text字段唯一,允许已软删除的同名Topic存在,同时支持重新创建同名活跃Topic,且删除新创建的Topic时无冲突。
具体实现步骤
1. 业务层前置校验(核心)
在创建或更新Topic时,先执行查询校验:
- 创建Topic:检查是否存在
status = 'active'且text与待创建值相同的记录。若存在则拒绝创建;若不存在(包括仅存在已删除的同名记录)则允许创建。
示例SQL:SELECT COUNT(id) FROM topic WHERE text = ? AND status = 'active' - 更新Topic的
text字段:若原Topic处于活跃状态,需检查新text是否被其他活跃Topic占用;若原Topic已删除,更新text时无需校验(通常删除状态的Topic不应被修改)。
2. 并发场景下的事务与锁控制
高并发场景中,可能出现多个请求同时校验通过后插入的情况,需通过事务+锁避免重复创建:
- 悲观锁方案:在校验查询时添加
FOR UPDATE(适用于InnoDB引擎),锁定符合条件的记录(无记录则锁定间隙),防止其他请求同时插入:
若查询返回结果,回滚事务并返回错误;否则插入新的活跃Topic后提交事务。-- 开启事务后执行 SELECT id FROM topic WHERE text = ? AND status = 'active' FOR UPDATE - 乐观锁方案:给
topic表添加version字段,创建/更新时校验版本号,适用于并发量中等、冲突概率较低的场景。
3. 软删除逻辑优化
删除Topic时,仅将status字段更新为deleted即可,无需处理其他已存在的同名删除记录。因为没有全局或复合唯一约束,更新操作不会触发冲突。
4. 可选:数据库层面添加部分唯一索引(辅助校验)
若数据库支持(如MySQL 5.7+、PostgreSQL),可创建部分唯一索引,仅对活跃状态的记录施加唯一约束,作为业务层校验的兜底:
-- MySQL 示例 CREATE UNIQUE INDEX idx_topic_text_active ON topic(text) WHERE status = 'active'; -- PostgreSQL 示例 CREATE UNIQUE INDEX idx_topic_text_active ON topic(text) WHERE status = 'active';
该索引仅阻止活跃状态的同名Topic,不限制已删除的同名记录,完美匹配需求。它不会像复合唯一约束那样导致删除失败,因为删除时只是修改status,不会触发索引冲突。
为什么之前的方案不可行?
- 单一
text唯一约束:无法重新创建已软删除的同名Topic; text + status复合唯一约束:重新创建同名活跃Topic后,删除该Topic时会将status改为deleted,与之前已删除的同名记录触发复合唯一冲突,导致删除失败。
内容的提问来源于stack exchange,提问作者Sanket Kale
相关产品推荐
相关产品推荐

