数据库间隔多日写入重复记录的原因排查求助
为什么你的Create动作会出现重复UID的记录?
兄弟,你遇到的这个重复记录问题我太熟了,结合你给出的代码片段,大概率是这几个原因在搞鬼,咱们一步步拆解:
最常见的元凶:并发请求的竞争窗口
你当前的逻辑是「先查UID是否存在 → 不存在就新建」,但这个操作不是原子性的!举个例子:
- 请求A过来,查数据库发现UID不存在,正准备执行新建操作;
- 就在请求A还没把新记录写入数据库的间隙,请求B也来了,同样查到UID不存在,也开始执行新建;
- 最后两个请求都完成写入,就出现了同UID的重复记录。
这个时间窗口可能很短(毫秒级),但只要并发量上来,或者网络/数据库有点延迟,就会触发,甚至间隔数天的情况也可能是因为重复的请求重试(比如前端重复提交、定时任务重复触发)。
数据库没加兜底的唯一约束
就算代码逻辑没问题,如果你的records表没给uid字段加唯一索引,那数据库本身是不会阻止重复插入的。哪怕你代码里做了检查,一旦出现并发或者代码疏漏,重复记录就会直接存进库。
代码逻辑的潜在漏洞
看你给出的代码片段写到「unle...」,应该是unless record吧?要确认这部分逻辑有没有写错:
- 是不是新建记录时忘了把
record_uid赋值给新记录的uid字段?比如写成了Record.create(params)而不是Record.create(uid: record_uid, **params); - 有没有可能
params.delete(:uid)之后,后续逻辑又把uid参数加回去了?或者参数处理时出现了覆盖?
怎么解决?给你几个靠谱的方案
1. 先给数据库加唯一约束(最关键的兜底)
这是必须做的,不管代码怎么优化,数据库层面的约束是最后一道防线。在Rails里生成迁移文件:
# 生成迁移 rails generate migration AddUniqueIndexToRecordsUid # 编辑迁移文件内容 add_index :records, :uid, unique: true # 执行迁移 rails db:migrate
这样之后,哪怕代码出现并发插入,数据库会直接抛出RecordNotUnique错误,不会让重复记录落地。
2. 用原子性的UPSERT操作替代分步逻辑
Rails 6及以上版本支持upsert方法,它会直接调用数据库的原生UPSERT语句(原子操作,不存在竞争窗口):
def create record_uid = params.delete(:uid) if record_uid.present? # 原子性更新或创建:如果UID存在就更新,不存在就新建 Record.upsert( { uid: record_uid }.merge(params), unique_by: :uid # 指定唯一约束的字段 ) # 之后可以获取这条记录 record = Record.find_by(uid: record_uid) else # 处理没有UID的情况 record = Record.create(params) end # 后续的响应逻辑 end
如果是Rails 6以下的版本,可以用「事务+捕获异常」的方式:
def create record_uid = params.delete(:uid) if record_uid.present? begin Record.transaction do # 先查,存在就更新 record = Record.find_by!(uid: record_uid) record.update!(params) end rescue ActiveRecord::RecordNotFound # 没查到就新建 record = Record.create!(uid: record_uid, **params) rescue ActiveRecord::RecordNotUnique # 捕获唯一约束错误,再去查并更新 record = Record.find_by(uid: record_uid) record.update!(params) if record end else record = Record.create(params) end end
3. 检查前端/请求来源的重复提交
如果重复记录的间隔是数秒、数分钟甚至更久,还要排查是不是前端重复提交(比如用户多次点击按钮)、定时任务重复触发、第三方回调重试导致的。可以在前端加防重复提交的逻辑(比如按钮置灰),或者在服务端对请求做幂等性校验。
内容的提问来源于stack exchange,提问作者JohnsWort
相关产品推荐
相关产品推荐

